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 { /// /// ROLE:Controls the entire build /// /// communicates via events to show progress /// 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; public event Action 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! throw new Exception("remove comment here"); //typeof(X86OpCodeMap).Equals(null); //TODO static hack BuilderStep.Completed += new Action(BuilderStep_Completed); BuilderStep.Started += new Action(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"); } /// /// 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. /// /// Full path to the Build directory. /// ///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 OnCompileCompleted(); 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 OnBuildCompleted(); } private void OnCompileCompleted() { if (CompileCompleted != null) CompileCompleted.Invoke(); LogTime("Compile complete"); } private void OnBuildCompleted() { if ( BuildCompleted != null) BuildCompleted.Invoke(); LogTime("Build complete"); } private static Process ProcessNonDebugBuilds(BuildOptions options, Process xQEMU) { switch (options.Target) { case "None": break; 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; //} throw new Exception("Fix building!"); } 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 , string target) { var options = Cosmos.Compiler.Builder.BuildOptions.Load(); options.DebugMode = mode; options.DebugPortId = portId; options.UseGDB = useGDB; options.Target = target; BeginCompile(options); } public string BuildPath { get { return buildPath; } } } }