mirror of
https://github.com/danbulant/Cosmos
synced 2026-05-19 20:39:01 +00:00
474 lines
23 KiB
C#
474 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
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 {
|
|
|
|
public class Builder {
|
|
public string BuildPath;
|
|
public readonly string ToolsPath;
|
|
public readonly string AsmPath;
|
|
public readonly Engine Engine = new Engine();
|
|
public bool UseInternalAssembler = false;
|
|
public Builder() {
|
|
BuildPath = GetBuildPath();
|
|
ToolsPath = BuildPath + @"Tools\";
|
|
AsmPath = ToolsPath + @"asm\";
|
|
// MtW: leave this here, otherwise VS wont copy required dependencies!
|
|
typeof(X86OpCodeMap).Equals(null);
|
|
|
|
}
|
|
|
|
/// <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>
|
|
protected static string GetBuildPath()
|
|
{
|
|
try
|
|
{
|
|
//string xResult = "";
|
|
Options.Load();
|
|
|
|
//Primary look in .XML
|
|
if (!String.IsNullOrEmpty(Options.BuildPath))
|
|
{
|
|
if (Directory.Exists(Options.BuildPath))
|
|
return Options.BuildPath;
|
|
else
|
|
Options.BuildPath = String.Empty; //Reset the path if it doesn't exist.
|
|
}
|
|
|
|
//Fallback to Registry
|
|
if (String.IsNullOrEmpty(Options.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.");
|
|
Options.BuildPath = xRegPath;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Fallback to calculating from current dir
|
|
if (string.IsNullOrEmpty(Options.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\";
|
|
Options.BuildPath = xCalculatedPath;
|
|
}
|
|
|
|
//If path not in .xml, Registry and also unable to calculate
|
|
if (string.IsNullOrEmpty(Options.BuildPath))
|
|
throw new DirectoryNotFoundException("Unable to find Cosmos Build Path!");
|
|
|
|
//Make sure path has a trailing slash
|
|
if (!Options.BuildPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
|
|
Options.BuildPath += Path.DirectorySeparatorChar;
|
|
|
|
Options.Save();
|
|
return Options.BuildPath;
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
throw new Exception("Error while getting Cosmos Build Path!", E);
|
|
}
|
|
}
|
|
|
|
protected void RemoveFile(string aPathname)
|
|
{
|
|
if (File.Exists(aPathname))
|
|
{
|
|
RemoveReadOnlyAttribute(aPathname);
|
|
File.Delete(aPathname);
|
|
}
|
|
}
|
|
|
|
protected void CopyFile(string aFrom, string aTo)
|
|
{
|
|
string xDir = Path.GetDirectoryName(aTo);
|
|
if (!Directory.Exists(xDir))
|
|
{
|
|
Directory.CreateDirectory(xDir);
|
|
}
|
|
File.Copy(aFrom, aTo);
|
|
}
|
|
|
|
protected void RemoveReadOnlyAttribute(string aPathname)
|
|
{
|
|
var xAttribs = File.GetAttributes(aPathname);
|
|
if ((xAttribs & FileAttributes.ReadOnly) > 0)
|
|
{
|
|
// This works because we only do this if Read only is already set
|
|
File.SetAttributes(aPathname, xAttribs ^ FileAttributes.ReadOnly);
|
|
}
|
|
}
|
|
|
|
public void MakeISO() {
|
|
string xPath = BuildPath + @"ISO\";
|
|
RemoveFile(BuildPath + "cosmos.iso");
|
|
RemoveFile(xPath + "output.bin");
|
|
CopyFile(BuildPath + "output.bin", xPath + "output.bin");
|
|
// From TFS its read only, mkisofs doesnt like that
|
|
RemoveReadOnlyAttribute(xPath + "isolinux.bin");
|
|
Global.Call(ToolsPath + @"mkisofs.exe", @"-R -b isolinux.bin -no-emul-boot -boot-load-size 4 -boot-info-table -o ..\Cosmos.iso .", xPath);
|
|
}
|
|
|
|
public event Action CompileCompleted;
|
|
public event Action<int, int> CompilingMethods;
|
|
public event Action<int, int> CompilingStaticFields;
|
|
public event Action<LogSeverityEnum, string> LogMessage;
|
|
private void OnCompilingMethods(int aCur, int aMax) {
|
|
if (CompilingMethods != null) { CompilingMethods(aCur, aMax); }
|
|
}
|
|
|
|
private void OnCompilingStaticFields(int aCur, int aMax) {
|
|
if (CompilingStaticFields != null) { CompilingStaticFields(aCur, aMax); }
|
|
}
|
|
|
|
private void OnLogMessage(LogSeverityEnum aSeverity, string aMessage) {
|
|
if (aSeverity == LogSeverityEnum.Informational) {
|
|
return; }
|
|
if (LogMessage != null) { LogMessage(aSeverity, aMessage); }
|
|
}
|
|
|
|
protected void ThreadExecute(object aParam) {
|
|
var xParam = (PassedEngineValue)aParam;
|
|
Engine.TraceAssemblies = xParam.TraceAssemblies;
|
|
Engine.CompilingMethods += OnCompilingMethods;
|
|
Engine.CompilingStaticFields += OnCompilingStaticFields;
|
|
try {
|
|
Engine.DebugLog += OnLogMessage;
|
|
Engine.Execute(xParam.aAssembly,
|
|
xParam.aTargetPlatform,
|
|
g => Path.Combine(AsmPath, g + ".asm"),
|
|
xParam.aPlugs,
|
|
xParam.aDebugMode,
|
|
xParam.GDBDebug,
|
|
1,
|
|
xParam.aOutputDir,
|
|
UseInternalAssembler);
|
|
}catch(Exception E) {
|
|
OnLogMessage(LogSeverityEnum.Error, E.ToString());
|
|
}
|
|
CompileCompleted.Invoke();
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
protected void MonoThreadExecute(object aParam) {
|
|
var xParam = (PassedEngineValue)aParam;
|
|
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:
|
|
CompileCompleted.Invoke();
|
|
}
|
|
|
|
// 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(DebugMode aDebugMode, byte aDebugComport, bool aGDB) {
|
|
if (!Directory.Exists(AsmPath)) {
|
|
Directory.CreateDirectory(AsmPath);
|
|
}
|
|
string[] xPlugs = GetPlugs();
|
|
var xEngineParams = new PassedEngineValue(TargetAssembly.Location, TargetPlatformEnum.X86
|
|
, xPlugs, aDebugMode, aDebugComport, aGDB, AsmPath, Options.TraceAssemblies);
|
|
|
|
if (Options.dotNETFrameworkImplementation == dotNETFrameworkImplementationEnum.Microsoft) {
|
|
var xThread = new Thread(new ParameterizedThreadStart(ThreadExecute));
|
|
xThread.Start(xEngineParams);
|
|
} else {
|
|
var xThread = new Thread(MonoThreadExecute);
|
|
xThread.Start(xEngineParams);
|
|
}
|
|
}
|
|
|
|
public void Assemble() {
|
|
RemoveFile(BuildPath + "output.obj");
|
|
Global.Call(ToolsPath + @"nasm\nasm.exe", String.Format("-g -f bin -o \"{0}\" \"{1}\"", BuildPath + "output.obj", AsmPath + "main.asm"), BuildPath);
|
|
}
|
|
|
|
public void Link() {
|
|
RemoveFile(BuildPath + "output.bin");
|
|
//Global.Call(ToolsPath + @"cygwin\ld.exe", String.Format("-Ttext 0x500000 -Tdata 0x200000 -e Kernel_Start -o \"{0}\" \"{1}\"", "output.bin", "output.obj"), BuildPath);
|
|
File.Move(Path.Combine(BuildPath, "output.obj"), Path.Combine(BuildPath, "output.bin"));
|
|
RemoveFile(BuildPath + "output.obj");
|
|
}
|
|
|
|
public void MakeVPC() {
|
|
MakeISO();
|
|
string xPath = BuildPath + @"VPC\";
|
|
RemoveReadOnlyAttribute(xPath + "Cosmos.vmc");
|
|
RemoveReadOnlyAttribute(xPath + "hda.vhd");
|
|
Process.Start(xPath + "Cosmos.vmc");
|
|
}
|
|
|
|
public void MakeVMWare(bool useVMWareServer) {
|
|
MakeISO();
|
|
string xPath = BuildPath + @"VMWare\";
|
|
|
|
if (useVMWareServer) {
|
|
xPath += @"Server\";
|
|
} else {
|
|
xPath += @"Workstation\";
|
|
}
|
|
|
|
RemoveReadOnlyAttribute(xPath + "Cosmos.nvram");
|
|
RemoveReadOnlyAttribute(xPath + "Cosmos.vmsd");
|
|
RemoveReadOnlyAttribute(xPath + "Cosmos.vmx");
|
|
RemoveReadOnlyAttribute(xPath + "Cosmos.vmxf");
|
|
RemoveReadOnlyAttribute(xPath + "hda.vmdk");
|
|
|
|
Process.Start(xPath + @"Cosmos.vmx");
|
|
}
|
|
|
|
public Process MakeQEMU(bool aUseHDImage, bool aGDB, bool aDebugger, string aDebugComMode, bool aUseNetworkTap, object aNetworkCard, object aAudioCard) {
|
|
MakeISO();
|
|
|
|
//From v0.9.1 Qemu requires forward slashes in path
|
|
BuildPath = BuildPath.Replace('\\', '/');
|
|
|
|
RemoveFile(BuildPath + "serial-debug.txt");
|
|
// QEMU Docs - http://fabrice.bellard.free.fr/qemu/qemu-doc.html
|
|
if (File.Exists(BuildPath + "COM1-output.dbg")) {
|
|
File.Delete(BuildPath + "COM1-output.dbg");
|
|
}
|
|
if (File.Exists(BuildPath + "COM2-output.dbg")) {
|
|
File.Delete(BuildPath + "COM2-output.dbg");
|
|
}
|
|
string xHDString = "";
|
|
if (aUseHDImage) {
|
|
if (File.Exists(BuildPath + "hda.img")) {
|
|
xHDString += "-hda \"" + BuildPath + "hda.img\" ";
|
|
}
|
|
if (File.Exists(BuildPath + "hdb.img")) {
|
|
xHDString += "-hdb \"" + BuildPath + "hdb.img\" ";
|
|
}
|
|
if (File.Exists(BuildPath + "hdd.img")) {
|
|
xHDString += "-hdb \"" + BuildPath + "hdd.img\" ";
|
|
}
|
|
}
|
|
|
|
var xProcess = Global.Call(ToolsPath + @"qemu\qemu.exe"
|
|
// HD image
|
|
, xHDString
|
|
// Path for BIOS, VGA BIOS, and keymaps
|
|
+ " -L ."
|
|
// CD ROM image
|
|
+ " -cdrom \"" + BuildPath + "Cosmos.iso\""
|
|
// Boot CD ROM
|
|
+ " -boot d"
|
|
// Audio hardware
|
|
+ (String.IsNullOrEmpty(aAudioCard as String) ? "" : " -soundhw " + aAudioCard)
|
|
// Setup serial port
|
|
// Might allow serial file later for post debugging of CPU
|
|
// etc since serial to TCP on a byte level is likely highly innefficient
|
|
// with the packet overhead
|
|
//
|
|
// COM1
|
|
+ (aDebugger ? " " + aDebugComMode : " -serial null")
|
|
// COM2
|
|
+ " -serial file:\"" + BuildPath + "COM2-output.dbg\""
|
|
// Enable acceleration if we are not using GDB
|
|
+ (aGDB ? " -S -s" : " -kernel-kqemu")
|
|
// Ethernet card
|
|
+ (String.IsNullOrEmpty(aNetworkCard as String) ? "" : string.Format(" -net nic,model={0},macaddr=52:54:00:12:34:57", aNetworkCard))
|
|
//+ " -redir tcp:5555::23" //use f.instance 'telnet localhost 5555' or 'http://localhost:5555/' to access machine
|
|
+ (String.IsNullOrEmpty(aNetworkCard as String) ? "" : (aUseNetworkTap ? " -net tap,ifname=CosmosTAP" : " -net user")) //requires TAP installed on development computer
|
|
, ToolsPath + @"qemu", false, true);
|
|
|
|
if (aGDB) {
|
|
//TODO: If the host is really busy, sometimes GDB can run before QEMU finishes loading.
|
|
//in this case, GDB says "program not running". Not sure how to fix this properly.
|
|
// Add a sleep? :)
|
|
Global.Call(ToolsPath + "gdb.exe"
|
|
, BuildPath + @"output.bin" + " --eval-command=\"target remote:1234\" --eval-command=\"b _CODE_REQUESTED_BREAK_\" --eval-command=\"c\""
|
|
, ToolsPath + @"qemu\", false, false);
|
|
}
|
|
return xProcess;
|
|
}
|
|
|
|
public void MakeUSB(char aDrive) {
|
|
string xPath = BuildPath + @"USB\";
|
|
RemoveFile(xPath + @"output.bin");
|
|
File.Move(BuildPath + @"output.bin", xPath + @"output.bin");
|
|
// Copy to USB device
|
|
RemoveFile(aDrive + @":\output.bin");
|
|
File.Copy(xPath + @"output.bin", aDrive + @":\output.bin");
|
|
RemoveFile(aDrive + @":\mboot.c32");
|
|
File.Copy(xPath + @"mboot.c32", aDrive + @":\mboot.c32");
|
|
RemoveFile(aDrive + @":\syslinux.cfg");
|
|
File.Copy(xPath + @"syslinux.cfg", aDrive + @":\syslinux.cfg");
|
|
// Set MBR
|
|
//TODO: Hangs on Windows Server 2008 - maybe needs admin permissions? Or maybe its not compat?
|
|
// Runs from command line ok in 2008.....
|
|
Global.Call(ToolsPath + "syslinux.exe", "-fma " + aDrive + ":", ToolsPath, true, true);
|
|
}
|
|
|
|
public void MakePXE() {
|
|
string xPath = BuildPath + @"PXE\";
|
|
RemoveFile(xPath + @"Boot\output.bin");
|
|
File.Move(BuildPath + "output.bin", xPath + @"Boot\output.bin");
|
|
// *Must* set working dir so tftpd32 will set itself to proper dir
|
|
Global.Call(xPath + "tftpd32.exe", "", xPath, false, true);
|
|
}
|
|
|
|
public void MakeVHD()
|
|
{
|
|
Environment.SetEnvironmentVariable("CosmosBuildPath", BuildPath);
|
|
Global.Call(Environment.GetEnvironmentVariable("WinDir")+ "\\system32\\diskpart /s "+BuildPath+"diskpart_script", "", BuildPath,true,false);
|
|
Environment.SetEnvironmentVariable("CosmosBuildPath","");
|
|
}
|
|
}
|
|
}
|