Cosmos/source/Cosmos.Build.Windows/Builder/Builder.cs

635 lines
25 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using Microsoft.Win32;
using Indy.IL2CPU;
using Indy.IL2CPU.IL.X86;
using IL2CPU;
using System.IO.Pipes;
namespace Cosmos.Compiler.Builder
{
/// <summary>
/// ROLE:Controls the entire build
///
/// communicates via events to show progress
/// </summary>
public class Builder : IBuilder
{
public readonly string ToolsPath;
public readonly string AsmPath;
public readonly Engine Engine = new Engine();
public event Action CompileCompleted;
public event Action BuildCompleted;
public event Action<BuildProgress> BuildProgress;
public event Action<LogSeverityEnum, string> LogMessage;
private DebugWindowController xDebugWindow = null; //HACK pass in event
public DebugWindowController DebugWindow { get { return xDebugWindow; } }
private readonly BuildProgress currentProgress = new BuildProgress();
private string buildPath;
// public bool UseInternalAssembler = false; //via options
public Builder()
{
buildPath = GetBuildPath();
ToolsPath = BuildPath + @"Tools\";
AsmPath = ToolsPath + @"asm\";
// MtW: leave this here, otherwise VS wont copy required dependencies!
typeof(X86OpCodeMap).Equals(null);
//TODO static hack
BuilderStep.Completed += new Action<string, object>(BuilderStep_Completed);
BuilderStep.Started += new Action<string>(BuilderStep_Started);
}
void BuilderStep_Started(string stepName)
{
currentProgress.Step = stepName;
OnLogMessage(LogSeverityEnum.Informational, stepName + "Started");
LogTime(stepName + "Started");
}
void BuilderStep_Completed(string stepName, object optionResult)
{
OnLogMessage(LogSeverityEnum.Informational, stepName + "Finished");
LogTime(stepName + "Finished");
}
/// <summary>
/// Retrieves the path to the Build directory.
/// First looks in the .xml file in %AppData%\Cosmos.
/// Secondly searches Registry.
/// If no match found there either it will use the Current Directory to calculate the path to the Build directory.
/// </summary>
/// <returns>Full path to the Build directory.</returns>
///
///TODO factor out
string GetBuildPath()
{
try
{
//string xResult = "";
var buildOptions = BuildOptions.Load();
//Primary look in .XML
if (!String.IsNullOrEmpty(buildOptions.BuildPath))
{
if (Directory.Exists(buildOptions.BuildPath))
return buildOptions.BuildPath;
else
buildOptions.BuildPath = String.Empty; //Reset the path if it doesn't exist.
}
//Fallback to Registry
if (String.IsNullOrEmpty(buildOptions.BuildPath))
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Cosmos");
if (key != null)
{
var xRegPath = key.GetValue("Build Path", String.Empty).ToString();
if (!String.IsNullOrEmpty(xRegPath))
{
if (!Directory.Exists(xRegPath))
throw new DirectoryNotFoundException(
"Found path to Build Path in registry, but directory does not exist.");
buildOptions.BuildPath = xRegPath;
}
}
}
//Fallback to calculating from current dir
if (string.IsNullOrEmpty(buildOptions.BuildPath))
{
var xCalculatedPath = Directory.GetCurrentDirectory().ToLowerInvariant();
int xPos = xCalculatedPath.LastIndexOf("source");
if (xPos == -1)
{
xPos = xCalculatedPath.LastIndexOf("buildoutput");
if (xPos == -1)
{
throw new Exception("Unable to find directory named 'BuildOutput' or 'Source' when using CurrentDirectory. (CurrentDirectory = '" + Environment.CurrentDirectory + "')");
}
}
xCalculatedPath = xCalculatedPath.Substring(0, xPos) + @"Build\";
buildOptions.BuildPath = xCalculatedPath;
}
//If path not in .xml, Registry and also unable to calculate
if (string.IsNullOrEmpty(buildOptions.BuildPath))
throw new DirectoryNotFoundException("Unable to find Cosmos Build Path!");
//Make sure path has a trailing slash
if (!buildOptions.BuildPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
buildOptions.BuildPath += Path.DirectorySeparatorChar;
buildOptions.Save();
return buildOptions.BuildPath;
}
catch (Exception E)
{
throw new Exception("Error while getting Cosmos Build Path!", E);
}
}
private void OnCompilingMethods(int aCur, int aMax)
{
if (aCur < currentProgress.MethodsProcessed
|| aMax < currentProgress.MaxMethods)
throw new ArgumentException("Trying to set lower");
currentProgress.MethodsProcessed = aCur;
currentProgress.MaxMethods = aMax;
OnBuildProgressChanged();
}
private void OnCompilingStaticFields(int aCur, int aMax)
{
if (aCur < currentProgress.FieldsProcessed
|| aMax < currentProgress.MaxFields)
throw new ArgumentException("Trying to set lower");
currentProgress.FieldsProcessed = aCur;
currentProgress.MaxFields = aMax;
OnBuildProgressChanged();
}
protected void OnBuildProgressChanged()
{
if (BuildProgress != null)
BuildProgress(currentProgress);
}
private void OnLogMessage(LogSeverityEnum aSeverity, string aMessage)
{
if (aSeverity == LogSeverityEnum.Informational)
{
return;
}
if (LogMessage != null) { LogMessage(aSeverity, aMessage); }
}
private void LogTime(string text)
{
OnLogMessage(LogSeverityEnum.Performance, text + " " + DateTime.Now.ToLongTimeString());
}
protected void ThreadExecute(object aParam)
{
var options = aParam as BuildOptions;
var xEngineParams = BuildOptionsToEngineParam(options);
LogTime("Thread execute start");
if (options.dotNETFrameworkImplementation == dotNETFrameworkImplementationEnum.Microsoft)
RunEngine(xEngineParams);
else
MonoCompile(xEngineParams);
//SIGNAL END COMPILE
CompileCompleted.Invoke();
LogTime("Compile complete");
if (options.CompileIL)
{
// We always show the window now since when its shown its
// for a short time and not in "paralell" as it was before.
if (options.UseInternalAssembler == false)
{
// ShowWindow(mConsoleWindow, 1); //HACK remove show and hide console we can put a command to the UI via an event if needed but its quick!
new AssembleStep(options).Execute();
new LinkStep(options).Execute();
// ShowWindow(mConsoleWindow, 0); //Hide!
}
}
Process xQEMU = null;
//TODO factor debug out and then fold into one method
if (options.DebugMode != DebugMode.None)
ProcessDebug(options, ref xDebugWindow, ref xQEMU);
else
xQEMU = ProcessNonDebugBuilds(options, xQEMU);
LogTime("Thread execute finish");
//SIGNAL END
BuildCompleted.Invoke();
}
private static Process ProcessNonDebugBuilds(BuildOptions options, Process xQEMU)
{
switch (options.Target)
{
case "QEMU":
var qemu = new MakeQEMUStep(options);
qemu.Execute();
xQEMU = qemu.Result;
break;
case "VMWare":
new MakeVMWareStep(options).Execute();
break;
case "VPC":
new MakeVPCStep(options).Execute();
break;
case "ISO":
new MakeISOStep(options).Execute();
break;
case "USB":
new MakeUSBStep(options).Execute();
break;
case "PXE":
new MakePXEStep(options).Execute();
break;
default:
throw new Exception("build mode not supported: " + options.Target);
} return xQEMU;
}
[Obsolete("Dont use")]
private PassedEngineValue BuildOptionsToEngineParam(BuildOptions options)
{
DebugMode aDebugMode = options.DebugMode; //TODO fix if not needed
byte aDebugComport = options.DebugPortId;
bool aGDB = options.UseGDB;
if (!Directory.Exists(AsmPath))
{
Directory.CreateDirectory(AsmPath);
}
string[] xPlugs = GetPlugs();
//TODO sort out
var xEngineParams = new PassedEngineValue(TargetAssembly.Location, TargetPlatformEnum.X86
, xPlugs, aDebugMode, aDebugComport, aGDB, AsmPath, options.TraceAssemblies);
return xEngineParams;
}
private void ProcessDebug(BuildOptions options, ref DebugWindowController xDebugWindow, ref Process xQEMU)
{
if (options.DebugMode == DebugMode.Source)
{
/*var xLabelByAddressMapping = ObjDump.GetLabelByAddressMapping(
mBuilder.BuildPath + "output.bin", mBuilder.ToolsPath + @"cygwin\objdump.exe");*/
var xLabelByAddressMapping = SourceInfo.ParseFile(BuildPath);
var xSourceMappings = SourceInfo.GetSourceInfo(xLabelByAddressMapping
, BuildPath + "Tools/asm/debug.cxdb");
DebugConnector xDebugConnector = null;
switch (options.Target)
{
case "QEMU":
if (options.DebugComMode ==
"TCP: Cosmos Debugger as server on port 4444, QEmu as client")
{
xDebugConnector = new DebugConnectorTCPServer();
Thread.Sleep(250);
var qemu = new MakeQEMUStep(options);
qemu.Execute();
xQEMU = qemu.Result;
}
else if (options.DebugComMode ==
"TCP: Cosmos Debugger as client, QEmu as server on port 4444")
{
var qemu = new MakeQEMUStep(options);
qemu.Execute();
xQEMU = qemu.Result; xDebugConnector = new DebugConnectorTCPClient();
}
else if (options.DebugComMode ==
"Named pipe: Cosmos Debugger as client, QEmu as server")
{
var qemu = new MakeQEMUStep(options);
qemu.Execute();
xQEMU = qemu.Result; xDebugConnector = new DebugConnectorPipeClient();
}
else if (options.DebugComMode ==
"Named pipe: Cosmos Debugger as server, QEmu as client")
{
xDebugConnector = new DebugConnectorPipeServer();
var qemu = new MakeQEMUStep(options);
qemu.Execute();
xQEMU = qemu.Result;
}
break;
case "VMWare":
xDebugConnector = new DebugConnectorPipeServer();
new MakeVMWareStep(options).Execute();
break;
case "USB":
xDebugConnector = new DebugConnectorSerial(options.DebugPortId);
break;
case "PXE":
xDebugConnector = new DebugConnectorSerial(options.DebugPortId);
break;
default:
throw new Exception("Debug mode not supported: " + options.DebugMode.ToString());
}
//TODO handle passing to UI better
xDebugWindow = new DebugWindowController();
xDebugWindow.mSourceMappings = xSourceMappings;
xDebugWindow.mDebugConnector = xDebugConnector;
}
else
{
throw new Exception("Debug mode not supported: " + options.DebugMode);
}
}
//TODO remove more configurtional so logic should be in confg not here
//TODO make a build step by cleaning up the engine params
private void RunEngine(object aParam)
{
var xParam = (PassedEngineValue)aParam;
Engine.TraceAssemblies = xParam.TraceAssemblies;
Engine.CompilingMethods += OnCompilingMethods;
Engine.CompilingStaticFields += OnCompilingStaticFields;
try
{
Engine.DebugLog += OnLogMessage;
LogTime("Engine execute start");
currentProgress.Step = "Engine Execute";
Engine.Execute(xParam.aAssembly,
xParam.aTargetPlatform,
g => Path.Combine(AsmPath, g + ".asm"),
xParam.aPlugs,
xParam.aDebugMode,
xParam.GDBDebug,
1,
xParam.aOutputDir,
(Cosmos.Compiler.Builder.BuildOptions.Load()).UseInternalAssembler); //HACK
LogTime("Engine execute finish");
}
catch (Exception E)
{
OnLogMessage(LogSeverityEnum.Error, E.ToString());
}
finally
{
Engine.CompilingMethods -= OnCompilingMethods;
Engine.CompilingStaticFields -= OnCompilingStaticFields;
}
}
private string GetMonoExeFilename()
{
return Path.Combine(Path.Combine(Path.Combine(Path.Combine(GetBuildPath(),
"Tools"),
"Mono"),
"bin"),
"mono.exe");
}
private string GetMonoLibDir()
{
return Path.Combine(Path.Combine(Path.Combine(GetBuildPath(),
"Tools"),
"Mono"),
"lib");
}
//TODO redesign
//protected void MonoThreadExecute(object aBuildOptions)
//{
// var options = aBuildOptions as BuildOptions;
// var xParam = BuildOptionsToEngineParam(options);
// MonoCompile(xParam);
// CompileCompleted.Invoke();
// RunBuildPhase();
//}
private void MonoCompile(PassedEngineValue xParam)
{
var xExeParamsBuilder = new StringBuilder();
xExeParamsBuilder.AppendFormat("\"{0}\" ",
typeof(IL2CPU.Program).Assembly.Location);
xExeParamsBuilder.AppendFormat("/assembly \"{0}\" ",
xParam.aAssembly);
if (xParam.aOutputDir.EndsWith("\\"))
{
xExeParamsBuilder.AppendFormat("/output \"{0}\\\" ",
xParam.aOutputDir);
}
else
{
xExeParamsBuilder.AppendFormat("/output \"{0}\" ",
xParam.aOutputDir);
}
xExeParamsBuilder.AppendFormat("/trace \"{0}\" ",
xParam.TraceAssemblies.ToString());
xExeParamsBuilder.AppendFormat("/target X86 ");
xExeParamsBuilder.AppendFormat("/comport 1 ");
foreach (var xPlug in xParam.aPlugs)
{
xExeParamsBuilder.AppendFormat("/plug \"{0}\" ",
xPlug);
}
xExeParamsBuilder.AppendFormat("/logpipe CosmosCompileLog");
//var xProcess = Process.Start(@"C:\Program Files\Mono-2.0.1\bin\mono.exe",
// xExeParamsBuilder.ToString());
var xProcessStartInfo = new ProcessStartInfo(GetMonoExeFilename());
//var xProcessStartInfo = new ProcessStartInfo(typeof(IL2CPU.Program).Assembly.Location);
xProcessStartInfo.Arguments = xExeParamsBuilder.ToString();
xProcessStartInfo.RedirectStandardError = true;
xProcessStartInfo.RedirectStandardInput = true;
xProcessStartInfo.RedirectStandardOutput = true;
xProcessStartInfo.EnvironmentVariables.Add("MONO_PATH", GetMonoLibDir());
xProcessStartInfo.UseShellExecute = false;
using (var xLogStream = new System.IO.Pipes.NamedPipeServerStream("CosmosCompileLog", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.WriteThrough))
{
AutoResetEvent xWaitForConEvent = new AutoResetEvent(false);
var xAsyncCookie = xLogStream.BeginWaitForConnection(delegate { xWaitForConEvent.Set(); }, null);
var xProcess = Process.Start(xProcessStartInfo);
try
{
while (!xProcess.HasExited)
{
if (xWaitForConEvent.WaitOne(100))
{
break;
}
}
xLogStream.EndWaitForConnection(xAsyncCookie);
bool xRunning = true;
do
{
//while (!xLogStream.DataAvailable) { Thread.Sleep(15); }
var xBuff = new byte[1];
if (xLogStream.Read(xBuff, 0, 1) != 1) { throw new Exception("No Command received!"); }
switch (xBuff[0])
{
case (byte)LogChannelCommand.CompilingMethods:
{
xBuff = new byte[8];
if (xLogStream.Read(xBuff, 0, 8) != 8) { throw new Exception("No complete CompilingMethods data received!"); }
var xCurrent = BitConverter.ToInt32(xBuff, 0);
var xMax = BitConverter.ToInt32(xBuff, 4);
OnCompilingMethods(xCurrent, xMax);
break;
}
case (byte)LogChannelCommand.CompilingFields:
{
xBuff = new byte[8];
if (xLogStream.Read(xBuff, 0, 8) != 8) { throw new Exception("No complete CompilingFields data received!"); }
var xCurrent = BitConverter.ToInt32(xBuff, 0);
var xMax = BitConverter.ToInt32(xBuff, 4);
OnCompilingStaticFields(xCurrent, xMax);
break;
}
case (byte)LogChannelCommand.LogMessage:
{
xBuff = new byte[5];
if (xLogStream.Read(xBuff, 0, 5) != 5) { throw new Exception("No complete LogMessage data received!"); }
var xSeverity = (LogSeverityEnum)xBuff[0];
var xStringLength = BitConverter.ToInt32(xBuff, 1);
xBuff = new byte[xStringLength];
if (xLogStream.Read(xBuff, 0, xStringLength) != xStringLength) { throw new Exception("No complete LogMessage data received! (2)"); }
var xMessage = Encoding.Unicode.GetString(xBuff);
OnLogMessage(xSeverity, xMessage);
break;
}
case (byte)LogChannelCommand.EndOfProcessing:
{
xRunning = false;
break;
}
}
} while (!xProcess.HasExited && xRunning);
}
catch (Exception E)
{
if (xLogStream.IsConnected && !xProcess.HasExited)
{
throw new Exception("Error while processing log command", E);
}
}
if (!xProcess.HasExited) { xProcess.Kill(); }
}
// at the end:
}
// MtW: added as field, so that it can be reused by the test runner
public Assembly TargetAssembly = Assembly.GetEntryAssembly();
public string[] GetPlugs()
{
string[] xPlugs;
if (File.Exists(Path.Combine(Path.Combine(ToolsPath, "Cosmos.Kernel.Plugs"), "Cosmos.Kernel.Plugs.dll")))
{
xPlugs = new string[] {
Path.Combine(Path.Combine(ToolsPath, "Cosmos.Kernel.Plugs"), "Cosmos.Kernel.Plugs.dll"),
Path.Combine(Path.Combine(ToolsPath, "Cosmos.Hardware.Plugs"), "Cosmos.Hardware.Plugs.dll"),
Path.Combine(Path.Combine(ToolsPath, "Cosmos.Sys.Plugs"), "Cosmos.Sys.Plugs.dll")
};
}
else
{
string xPath = Path.GetDirectoryName(typeof(Builder).Assembly.Location);
xPlugs = new string[] {
Path.Combine(xPath, "Cosmos.Kernel.Plugs.dll"),
Path.Combine(xPath, "Cosmos.Hardware.Plugs.dll"),
Path.Combine(xPath, "Cosmos.Sys.Plugs.dll")
};
}
return xPlugs;
}
public void BeginCompile(BuildOptions options)
{
var xThread = new Thread(new ParameterizedThreadStart(ThreadExecute));
xThread.Start(options);
}
[Obsolete]
public void BeginCompile(DebugMode mode , byte portId , bool useGDB)
{
var options = Cosmos.Compiler.Builder.BuildOptions.Load();
options.DebugMode = mode;
options.DebugPortId = portId;
options.UseGDB = useGDB;
BeginCompile(options);
}
public string BuildPath
{
get { return buildPath; }
}
}
}