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(stream); return file != null; } } catch { return false; } } } public sealed class ProtoParseException : Exception { public ProtoParseException(string file) : base("An error occurred parsing " + file) { } } }