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...
}
}
}