using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BansheeEngine; namespace BansheeEditor { public enum ScriptAssemblyType { Game, Editor } public struct CompileData { public string[] files; public string defines; } public class CompilerInstance { private Process process; private Thread readErrorsThread; private List errors = new List(); private List warnings = new List(); private Regex compileErrorRegex = new Regex(@"\s*(?.*)\(\s*(?\d+)\s*,\s*(?\d+)\s*\)\s*:\s*(?warning|error)\s+(.*):\s*(?.*)"); private Regex compilerErrorRegex = new Regex(@"\s*error[^:]*:\s*(?.*)"); internal CompilerInstance(string[] files, string defines, string[] assemblyFolders, string[] assemblies, bool debugBuild, string outputFile) { ProcessStartInfo procStartInfo = new ProcessStartInfo(); StringBuilder argumentsBuilder = new StringBuilder(); if (!string.IsNullOrEmpty(defines)) argumentsBuilder.Append(" -d:" + defines); if (assemblyFolders != null && assemblyFolders.Length > 0) { argumentsBuilder.Append(" -lib:"); for (int i = 0; i < assemblyFolders.Length - 1; i++) argumentsBuilder.Append(assemblyFolders[i] + ","); argumentsBuilder.Append(assemblyFolders[assemblyFolders.Length - 1]); } if (assemblies != null && assemblies.Length > 0) { argumentsBuilder.Append(" -r:"); for (int i = 0; i < assemblies.Length - 1; i++) argumentsBuilder.Append(assemblies[i] + ","); argumentsBuilder.Append(assemblies[assemblies.Length - 1]); } if (debugBuild) argumentsBuilder.Append(" -debug+ -o-"); else argumentsBuilder.Append(" -debug- -o+"); argumentsBuilder.Append(" -target:library -out:" + "\"" + outputFile + "\""); for (int i = 0; i < files.Length; i++) argumentsBuilder.Append(" \"" + files[i] + "\""); if (File.Exists(outputFile)) File.Delete(outputFile); string outputDir = Path.GetDirectoryName(outputFile); if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir); procStartInfo.Arguments = argumentsBuilder.ToString(); procStartInfo.CreateNoWindow = true; procStartInfo.FileName = EditorApplication.CompilerPath; procStartInfo.RedirectStandardError = true; procStartInfo.RedirectStandardOutput = false; procStartInfo.UseShellExecute = false; procStartInfo.WorkingDirectory = EditorApplication.ProjectPath; process = new Process(); process.StartInfo = procStartInfo; process.Start(); readErrorsThread = new Thread(ReadErrorStream); readErrorsThread.Start(); } private void ReadErrorStream() { while (true) { if (process == null || process.HasExited) return; string line = process.StandardError.ReadLine(); if (string.IsNullOrEmpty(line)) continue; CompilerMessage message; if (TryParseCompilerMessage(line, out message)) { if (message.type == CompilerMessageType.Warning) { lock (warnings) warnings.Add(message); } else if (message.type == CompilerMessageType.Error) { lock (errors) errors.Add(message); } } } } private bool TryParseCompilerMessage(string messageText, out CompilerMessage message) { message = new CompilerMessage(); Match matchCompile = compileErrorRegex.Match(messageText); if (matchCompile.Success) { message.file = matchCompile.Groups["file"].Value; message.line = Int32.Parse(matchCompile.Groups["line"].Value); message.column = Int32.Parse(matchCompile.Groups["column"].Value); message.type = matchCompile.Groups["type"].Value == "error" ? CompilerMessageType.Error : CompilerMessageType.Warning; message.message = matchCompile.Groups["message"].Value; return true; } Match matchCompiler = compilerErrorRegex.Match(messageText); if (matchCompiler.Success) { message.file = ""; message.line = 0; message.column = 0; message.type = CompilerMessageType.Error; message.message = matchCompiler.Groups["message"].Value; return true; } return false; } public bool IsDone { get { return process.HasExited && readErrorsThread.ThreadState == System.Threading.ThreadState.Stopped; } } public bool HasErrors { get { return IsDone && process.ExitCode != 0; } } public CompilerMessage[] WarningMessages { get { lock (warnings) { return warnings.ToArray(); } } } public CompilerMessage[] ErrorMessages { get { lock (errors) { return errors.ToArray(); } } } public void Dispose() { if (process == null) return; if (!process.HasExited) { process.Kill(); process.WaitForExit(); } process.Dispose(); } } public static class ScriptCompiler { public static CompilerInstance CompileAsync(ScriptAssemblyType type, PlatformType platform, bool debug, string outputDir) { LibraryEntry[] scriptEntries = ProjectLibrary.Search("*", new ResourceType[] { ResourceType.ScriptCode }); List scriptFiles = new List(); for (int i = 0; i < scriptEntries.Length; i++) { if(scriptEntries[i].Type != LibraryEntryType.File) continue; FileEntry fileEntry = (FileEntry)scriptEntries[i]; ScriptCodeImportOptions io = (ScriptCodeImportOptions) fileEntry.Options; if (io.EditorScript && type == ScriptAssemblyType.Editor || !io.EditorScript && type == ScriptAssemblyType.Game) { scriptFiles.Add(scriptEntries[i].Path); } } string[] assemblyFolders; string[] assemblies; string outputFile; string[] frameworkAssemblies = BuildManager.GetFrameworkAssemblies(platform); if (type == ScriptAssemblyType.Game) { assemblyFolders = new string[] { EditorApplication.BuiltinAssemblyPath, EditorApplication.FrameworkAssemblyPath }; assemblies = new string[frameworkAssemblies.Length + 1]; assemblies[assemblies.Length - 1] = EditorApplication.EngineAssembly; outputFile = Path.Combine(outputDir, EditorApplication.ScriptGameAssembly); } else { assemblyFolders = new string[] { EditorApplication.BuiltinAssemblyPath, EditorApplication.FrameworkAssemblyPath, EditorApplication.ScriptAssemblyPath }; assemblies = new string[frameworkAssemblies.Length + 3]; assemblies[assemblies.Length - 1] = EditorApplication.EngineAssembly; assemblies[assemblies.Length - 2] = EditorApplication.EditorAssembly; assemblies[assemblies.Length - 3] = EditorApplication.ScriptGameAssembly; outputFile = Path.Combine(outputDir, EditorApplication.ScriptEditorAssembly); } Array.Copy(frameworkAssemblies, assemblies, frameworkAssemblies.Length); string defines = BuildManager.GetDefines(platform); return new CompilerInstance(scriptFiles.ToArray(), defines, assemblyFolders, assemblies, debug, outputFile); } } public enum CompilerMessageType { Warning, Error } public struct CompilerMessage { public CompilerMessageType type; public string message; public string file; public int line; public int column; } }