using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Tango.Core.Helpers; using Tango.Core.IO; using Tango.Logging; namespace Tango.Protobuf { /// /// Represents a protobuf compiler base class. /// /// public abstract class ProtoCompiler : IProtoCompiler { private const String COMPILERS_FOLDER_NAME = "ProtoCompilers"; //Compilers folder name. protected String _compilersPath; //Compilers folder path. private LogManager logManager = LogManager.Default; private int _currentProgress; private int _totalProgress; /// /// Occurs when the compiler has made some progress. /// public event EventHandler CompilationProgress; /// /// Gets the compiler language. /// public abstract CompilerLanguage Language { get; } /// /// Gets the proto imports folders. /// public List ImportsFolders { get; private set; } /// /// Gets a value indicating whether this compiler uses the default folder structure when generating code. /// public virtual bool UsesDefaultStructure { get; } /// /// Initializes a new instance of the class. /// public ProtoCompiler() { ImportsFolders = new List(); _compilersPath = Path.Combine(AssemblyHelper.GetCurrentAssemblyFolder(), COMPILERS_FOLDER_NAME); } /// /// Compiles the specified .proto message file. /// /// .proto file to compile /// /// A list of compiled results. /// public virtual IEnumerable CompileFile(string inputFile) { logManager.Log("Compiling file " + inputFile); CompilationProgress?.Invoke(this, new CompilerProgressEventArgs() { Current = _currentProgress++, Total = _totalProgress, File = inputFile, }); var tmpPath = TemporaryManager.Default.CreateFolder(); logManager.Log("Temp path: " + tmpPath); String importsString = "--proto_path \"" + Path.GetDirectoryName(inputFile) + "\" "; logManager.Log("Added import string: " + importsString); foreach (var path in ImportsFolders) { String importStr = "--proto_path \"" + path + "\" "; importsString += importStr; logManager.Log("Added import string: " + importStr); } Process p = new Process(); logManager.Log("Compilers folder path: " + _compilersPath); p.StartInfo.WorkingDirectory = _compilersPath; String oldCurrentDirectory = Environment.CurrentDirectory; Environment.CurrentDirectory = _compilersPath; p.StartInfo.FileName = GetProtoCompilerName(); logManager.Log("Protobuf executable path: " + p.StartInfo.FileName); p.StartInfo.Arguments = String.Format( "{0} {1}=\"{2}\" \"{3}\"", importsString, GetProtoArguments(), tmpPath, inputFile); logManager.Log("Final arguments:\n" + p.StartInfo.Arguments); p.StartInfo.CreateNoWindow = true; p.StartInfo.UseShellExecute = false; p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; p.StartInfo.RedirectStandardError = true; p.StartInfo.RedirectStandardOutput = true; logManager.Log("Executing compilation..."); p.Start(); p.WaitForExit(5000); Environment.CurrentDirectory = oldCurrentDirectory; String error = p.StandardError.ReadToEnd(); if (!String.IsNullOrWhiteSpace(error)) { var lines = error.Split(new[] { '\r', '\n' }); throw logManager.Log(new CompilerException() { Issues = lines.Where(x => x.Length > 0).ToList() }); } List results = new List(); foreach (var file in Directory.GetFiles(tmpPath, "*.*", SearchOption.AllDirectories)) { CompilerFileResult result = new CompilerFileResult(Language, inputFile, Path.GetFileName(file), Path.GetDirectoryName(file).Replace(tmpPath, "").TrimStart('\\', '\\'), File.ReadAllText(file)); results.Add(result); } if (tmpPath.Delete()) { logManager.Log("Removed temp path: " + tmpPath); } else { logManager.Log("Could not remove temp path: " + tmpPath); } logManager.Log(Path.GetFileName(inputFile) + "compiled!"); return results; } /// /// Compiles the specified .proto message file asynchronously. /// /// .proto file to compile /// /// A list of compiled results. /// public async Task> CompileFileAsync(string inputFile) { return await new Task>(() => { return CompileFile(inputFile); }); } /// /// Compiles all files in the specified folder recursively. /// /// The source folder /// /// Compilation result. /// public virtual CompilerFolderResult CompileFolder(string sourceFolder, params String[] includeFolders) { _currentProgress = 0; _totalProgress = Directory.GetFiles(sourceFolder, "*.proto", SearchOption.AllDirectories).Length; if (!UsesDefaultStructure) { logManager.Log("Compiling folder: " + sourceFolder); ImportsFolders.Clear(); ImportsFolders.AddRange(Directory.GetDirectories(sourceFolder, "*.*", SearchOption.AllDirectories)); var result = CompileFolder(sourceFolder, sourceFolder); logManager.Log(Path.GetFileName(sourceFolder) + "compiled!"); return OnPostProcessFolderCompilation(result); } else { return CompileFolderDefault(sourceFolder); } } /// /// Compiles all files in the specified folder recursively. /// /// The source folder /// /// Compilation result. /// protected virtual CompilerFolderResult CompileFolder(string rootFolder, string sourceFolder) { List currentFolderResults = new List(); CompilerFolderResult currentFolder = new CompilerFolderResult(currentFolderResults, Language, sourceFolder, sourceFolder.Replace(rootFolder, "")); foreach (String file in Directory.GetFiles(sourceFolder, "*.proto")) { currentFolderResults.AddRange(CompileFile(file)); } foreach (string folder in Directory.GetDirectories(sourceFolder)) { if (Directory.GetFiles(folder, "*.proto").Length > 0) { currentFolderResults.Add(CompileFolder(rootFolder, folder)); } } return currentFolder; } /// /// Compiles all files in the specified folder recursively and asynchronously. /// /// The source folder /// /// Compilation result. /// public async Task CompileFolderAsync(string sourceFolder) { return await new Task(() => { return CompileFolder(sourceFolder); }); } /// /// Compiles all files in the specified folder recursively using the default structure. /// /// The source folder /// /// Compilation result. /// private CompilerFolderResult CompileFolderDefault(string sourceFolder) { logManager.Log("Compiling folder: " + sourceFolder); Dictionary fileResults = new Dictionary(); ImportsFolders.Clear(); ImportsFolders.AddRange(Directory.GetDirectories(sourceFolder, "*.*", SearchOption.AllDirectories)); var tempPath = TemporaryManager.Default.CreateFolder(); foreach (var file in Directory.GetFiles(sourceFolder, "*.proto", SearchOption.AllDirectories)) { foreach (var fileResult in CompileFile(file)) { String targetFolder = Path.Combine(tempPath, fileResult.RelativePath); String targetFile = Path.Combine(targetFolder, fileResult.Name); Directory.CreateDirectory(targetFolder); File.WriteAllText(targetFile, fileResult.Content); fileResults.Add(targetFile, fileResult); } } var result = CompileFolderDefault(tempPath, tempPath, fileResults); tempPath.Delete(); logManager.Log(Path.GetFileName(sourceFolder) + "compiled!"); return OnPostProcessFolderCompilation(result); } /// /// Compiles all files in the specified folder recursively using the default structure. /// /// /// The source folder /// /// Compilation result. /// private CompilerFolderResult CompileFolderDefault(string rootFolder, string sourceFolder, Dictionary fileResults) { List currentFolderResults = new List(); CompilerFolderResult currentFolder = new CompilerFolderResult(currentFolderResults, Language, sourceFolder, Path.GetFileName(sourceFolder)); foreach (String file in Directory.GetFiles(sourceFolder, "*.*")) { currentFolderResults.Add(fileResults[file]); } foreach (string folder in Directory.GetDirectories(sourceFolder)) { currentFolderResults.Add(CompileFolderDefault(rootFolder, folder, fileResults)); } return currentFolder; } /// /// Override in order to manipulate the folder compilation result. /// /// The result. /// protected virtual CompilerFolderResult OnPostProcessFolderCompilation(CompilerFolderResult result) { return result; } /// /// Gets the protobuf compiler CLI arguments (without input/output files!). /// /// protected abstract String GetProtoArguments(); /// /// Gets the protobuf compiler CLI file name (override when using a compiler other than the default 'protoc.exe'). /// /// /// The compiler program must be located in the compilers folder. /// /// protected virtual String GetProtoCompilerName() { return "protoc.exe"; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { //TODO: Dispose... } } }