123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- using System;
- using google.protobuf;
- using System.IO;
- using System.Diagnostics;
- using System.Text;
- using System.Threading;
- using System.Reflection;
- namespace ProtoBuf.CodeGenerator
- {
- public static class InputFileLoader
- {
- public static void Merge(FileDescriptorSet files, string path, TextWriter stderr, params string[] args)
- {
- if (stderr == null) throw new ArgumentNullException("stderr");
- if (files == null) throw new ArgumentNullException("files");
- if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");
- bool deletePath = false;
- if (!IsValidBinary(path))
- {
- // try to use protoc
- path = CompileDescriptor(path, stderr, args);
- deletePath = true;
- }
- try
- {
- using (FileStream stream = File.OpenRead(path))
- {
- Serializer.Merge(stream, files);
- }
- }
- finally
- {
- if (deletePath)
- {
- File.Delete(path);
- }
- }
- }
- public static string CombinePathFromAppRoot(string path)
- {
- string loaderPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
- if (!string.IsNullOrEmpty(loaderPath)
- && loaderPath[loaderPath.Length - 1] != Path.DirectorySeparatorChar
- && loaderPath[loaderPath.Length - 1] != Path.AltDirectorySeparatorChar)
- {
- loaderPath += Path.DirectorySeparatorChar;
- }
- if (loaderPath.StartsWith(@"file:\"))
- {
- loaderPath = loaderPath.Substring(6);
- }
- return Path.Combine(Path.GetDirectoryName(loaderPath), path);
- }
- public static string GetProtocPath(out string folder)
- {
- const string Name = "protoc.exe";
- string lazyPath = InputFileLoader.CombinePathFromAppRoot(Name);
- if (File.Exists(lazyPath))
- { // use protoc.exe from the existing location (faster)
- folder = null;
- return lazyPath;
- }
- // protogen.exe can be run with mono on Mac/Unix (cool mono)
- // but the embedded protoc.exe cannot be executed, as it's not a .net exe
- // workaround 1: ln -s /opt/local/bin/protoc protoc.exe
- // workaround 2: search the protoc in following bin folder
- string[] UnixProtoc = {
- "/usr/bin/protoc",
- "/usr/local/bin/protoc",
- "/opt/local/bin/protoc"
- };
- for (int i = 0; i < UnixProtoc.Length; i++)
- {
- if (File.Exists(UnixProtoc[i]))
- {
- folder = null;
- return UnixProtoc[i];
- }
- }
- folder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("n"));
- Directory.CreateDirectory(folder);
- string path = Path.Combine(folder, Name);
- // look inside ourselves...
- using (Stream resStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(
- typeof(InputFileLoader).Namespace + "." + Name))
- using (Stream outFile = File.OpenWrite(path))
- {
- long len = 0;
- int bytesRead;
- byte[] buffer = new byte[4096];
- while ((bytesRead = resStream.Read(buffer, 0, buffer.Length)) > 0)
- {
- outFile.Write(buffer, 0, bytesRead);
- len += bytesRead;
- }
- outFile.SetLength(len);
- }
- return path;
- }
- private static string CompileDescriptor(string path, TextWriter stderr, params string[] args)
- {
- path = Path.GetFullPath(path);
- string tmp = Path.GetTempFileName();
- string tmpFolder = null, protocPath = null;
- try
- {
- string arguments = string.Format(@"""--include_source_info"" ""--descriptor_set_out={0}"" ""--proto_path={1}"" ""--proto_path={2}"" ""--proto_path={3}"" --error_format=gcc ""{4}"" {5}",
- tmp, // output file
- Path.GetDirectoryName(path), // primary search path
- Environment.CurrentDirectory, // primary search path
- Path.GetDirectoryName(protocPath), // secondary search path
- Path.Combine(Environment.CurrentDirectory, path), // input file
- string.Join(" ", args) // extra args
- );
- protocPath = GetProtocPath(out tmpFolder);
- ProcessStartInfo psi = new ProcessStartInfo(protocPath, arguments);
- Debug.WriteLine(psi.FileName + " " + psi.Arguments, "protoc");
- psi.CreateNoWindow = true;
- psi.WindowStyle = ProcessWindowStyle.Hidden;
- psi.WorkingDirectory = Environment.CurrentDirectory;
- psi.UseShellExecute = false;
- psi.RedirectStandardOutput = psi.RedirectStandardError = true;
- using (Process proc = Process.Start(psi))
- {
- Thread errThread = new Thread(DumpStream(proc.StandardError, stderr));
- Thread outThread = new Thread(DumpStream(proc.StandardOutput, stderr));
- errThread.Name = "stderr reader";
- outThread.Name = "stdout reader";
- errThread.Start();
- outThread.Start();
- proc.WaitForExit();
- outThread.Join();
- errThread.Join();
- if (proc.ExitCode != 0)
- {
- if (HasByteOrderMark(path))
- {
- stderr.WriteLine("The input file should be UTF8 without a byte-order-mark (in Visual Studio use \"File\" -> \"Advanced Save Options...\" to rectify)");
- }
- throw new ProtoParseException(Path.GetFileName(path));
- }
- return tmp;
- }
- }
- catch
- {
- try { if (File.Exists(tmp)) File.Delete(tmp); }
- catch { } // swallow
- throw;
- }
- finally
- {
- if (!string.IsNullOrEmpty(tmpFolder))
- {
- try { Directory.Delete(tmpFolder, true); }
- catch { } // swallow
- }
- }
- }
- private static bool HasByteOrderMark(string path)
- {
- try
- {
- using (Stream s = File.OpenRead(path))
- {
- return s.ReadByte() > 127;
- }
- }
- catch (Exception ex)
- {
- Debug.WriteLine(ex); // log only
- return false;
- }
- }
- static ThreadStart DumpStream(TextReader reader, TextWriter writer)
- {
- return (ThreadStart)delegate
- {
- string line;
- while ((line = reader.ReadLine()) != null)
- {
- Debug.WriteLine(line);
- writer.WriteLine(line);
- }
- };
- }
- static bool IsValidBinary(string path)
- {
- try
- {
- using (FileStream stream = File.OpenRead(path))
- {
- FileDescriptorSet file = Serializer.Deserialize<FileDescriptorSet>(stream);
- return file != null;
- }
- }
- catch
- {
- return false;
- }
- }
- }
- public sealed class ProtoParseException : Exception
- {
- public ProtoParseException(string file) : base("An error occurred parsing " + file) { }
- }
- }
|