InputFileLoader.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. using System;
  2. using google.protobuf;
  3. using System.IO;
  4. using System.Diagnostics;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Reflection;
  8. namespace ProtoBuf.CodeGenerator
  9. {
  10. public static class InputFileLoader
  11. {
  12. public static void Merge(FileDescriptorSet files, string path, TextWriter stderr, params string[] args)
  13. {
  14. if (stderr == null) throw new ArgumentNullException("stderr");
  15. if (files == null) throw new ArgumentNullException("files");
  16. if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path");
  17. bool deletePath = false;
  18. if (!IsValidBinary(path))
  19. {
  20. // try to use protoc
  21. path = CompileDescriptor(path, stderr, args);
  22. deletePath = true;
  23. }
  24. try
  25. {
  26. using (FileStream stream = File.OpenRead(path))
  27. {
  28. Serializer.Merge(stream, files);
  29. }
  30. }
  31. finally
  32. {
  33. if (deletePath)
  34. {
  35. File.Delete(path);
  36. }
  37. }
  38. }
  39. public static string CombinePathFromAppRoot(string path)
  40. {
  41. string loaderPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
  42. if (!string.IsNullOrEmpty(loaderPath)
  43. && loaderPath[loaderPath.Length - 1] != Path.DirectorySeparatorChar
  44. && loaderPath[loaderPath.Length - 1] != Path.AltDirectorySeparatorChar)
  45. {
  46. loaderPath += Path.DirectorySeparatorChar;
  47. }
  48. if (loaderPath.StartsWith(@"file:\"))
  49. {
  50. loaderPath = loaderPath.Substring(6);
  51. }
  52. return Path.Combine(Path.GetDirectoryName(loaderPath), path);
  53. }
  54. public static string GetProtocPath(out string folder)
  55. {
  56. const string Name = "protoc.exe";
  57. string lazyPath = InputFileLoader.CombinePathFromAppRoot(Name);
  58. if (File.Exists(lazyPath))
  59. { // use protoc.exe from the existing location (faster)
  60. folder = null;
  61. return lazyPath;
  62. }
  63. // protogen.exe can be run with mono on Mac/Unix (cool mono)
  64. // but the embedded protoc.exe cannot be executed, as it's not a .net exe
  65. // workaround 1: ln -s /opt/local/bin/protoc protoc.exe
  66. // workaround 2: search the protoc in following bin folder
  67. string[] UnixProtoc = {
  68. "/usr/bin/protoc",
  69. "/usr/local/bin/protoc",
  70. "/opt/local/bin/protoc"
  71. };
  72. for (int i = 0; i < UnixProtoc.Length; i++)
  73. {
  74. if (File.Exists(UnixProtoc[i]))
  75. {
  76. folder = null;
  77. return UnixProtoc[i];
  78. }
  79. }
  80. folder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("n"));
  81. Directory.CreateDirectory(folder);
  82. string path = Path.Combine(folder, Name);
  83. // look inside ourselves...
  84. using (Stream resStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(
  85. typeof(InputFileLoader).Namespace + "." + Name))
  86. using (Stream outFile = File.OpenWrite(path))
  87. {
  88. long len = 0;
  89. int bytesRead;
  90. byte[] buffer = new byte[4096];
  91. while ((bytesRead = resStream.Read(buffer, 0, buffer.Length)) > 0)
  92. {
  93. outFile.Write(buffer, 0, bytesRead);
  94. len += bytesRead;
  95. }
  96. outFile.SetLength(len);
  97. }
  98. return path;
  99. }
  100. private static string CompileDescriptor(string path, TextWriter stderr, params string[] args)
  101. {
  102. path = Path.GetFullPath(path);
  103. string tmp = Path.GetTempFileName();
  104. string tmpFolder = null, protocPath = null;
  105. try
  106. {
  107. 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}",
  108. tmp, // output file
  109. Path.GetDirectoryName(path), // primary search path
  110. Environment.CurrentDirectory, // primary search path
  111. Path.GetDirectoryName(protocPath), // secondary search path
  112. Path.Combine(Environment.CurrentDirectory, path), // input file
  113. string.Join(" ", args) // extra args
  114. );
  115. protocPath = GetProtocPath(out tmpFolder);
  116. ProcessStartInfo psi = new ProcessStartInfo(protocPath, arguments);
  117. Debug.WriteLine(psi.FileName + " " + psi.Arguments, "protoc");
  118. psi.CreateNoWindow = true;
  119. psi.WindowStyle = ProcessWindowStyle.Hidden;
  120. psi.WorkingDirectory = Environment.CurrentDirectory;
  121. psi.UseShellExecute = false;
  122. psi.RedirectStandardOutput = psi.RedirectStandardError = true;
  123. using (Process proc = Process.Start(psi))
  124. {
  125. Thread errThread = new Thread(DumpStream(proc.StandardError, stderr));
  126. Thread outThread = new Thread(DumpStream(proc.StandardOutput, stderr));
  127. errThread.Name = "stderr reader";
  128. outThread.Name = "stdout reader";
  129. errThread.Start();
  130. outThread.Start();
  131. proc.WaitForExit();
  132. outThread.Join();
  133. errThread.Join();
  134. if (proc.ExitCode != 0)
  135. {
  136. if (HasByteOrderMark(path))
  137. {
  138. stderr.WriteLine("The input file should be UTF8 without a byte-order-mark (in Visual Studio use \"File\" -> \"Advanced Save Options...\" to rectify)");
  139. }
  140. throw new ProtoParseException(Path.GetFileName(path));
  141. }
  142. return tmp;
  143. }
  144. }
  145. catch
  146. {
  147. try { if (File.Exists(tmp)) File.Delete(tmp); }
  148. catch { } // swallow
  149. throw;
  150. }
  151. finally
  152. {
  153. if (!string.IsNullOrEmpty(tmpFolder))
  154. {
  155. try { Directory.Delete(tmpFolder, true); }
  156. catch { } // swallow
  157. }
  158. }
  159. }
  160. private static bool HasByteOrderMark(string path)
  161. {
  162. try
  163. {
  164. using (Stream s = File.OpenRead(path))
  165. {
  166. return s.ReadByte() > 127;
  167. }
  168. }
  169. catch (Exception ex)
  170. {
  171. Debug.WriteLine(ex); // log only
  172. return false;
  173. }
  174. }
  175. static ThreadStart DumpStream(TextReader reader, TextWriter writer)
  176. {
  177. return (ThreadStart)delegate
  178. {
  179. string line;
  180. while ((line = reader.ReadLine()) != null)
  181. {
  182. Debug.WriteLine(line);
  183. writer.WriteLine(line);
  184. }
  185. };
  186. }
  187. static bool IsValidBinary(string path)
  188. {
  189. try
  190. {
  191. using (FileStream stream = File.OpenRead(path))
  192. {
  193. FileDescriptorSet file = Serializer.Deserialize<FileDescriptorSet>(stream);
  194. return file != null;
  195. }
  196. }
  197. catch
  198. {
  199. return false;
  200. }
  201. }
  202. }
  203. public sealed class ProtoParseException : Exception
  204. {
  205. public ProtoParseException(string file) : base("An error occurred parsing " + file) { }
  206. }
  207. }