ProgramPreprocess.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. namespace Google.ProtocolBuffers.ProtoGen
  8. {
  9. /// <summary>
  10. /// Preprocesses any input files with an extension of '.proto' by running protoc.exe. If arguments
  11. /// are supplied with '--' prefix they are provided to protoc.exe, otherwise they are assumed to
  12. /// be used for ProtoGen.exe which is run on the resulting output proto buffer. If the option
  13. /// --descriptor_set_out= is specified the proto buffer file is kept, otherwise it will be removed
  14. /// after code generation.
  15. /// </summary>
  16. public class ProgramPreprocess
  17. {
  18. private const string ProtocExecutable = "protoc.exe";
  19. private const string ProtocDirectoryArg = "--protoc_dir=";
  20. private static int Main(string[] args)
  21. {
  22. try
  23. {
  24. return Environment.ExitCode = Run(args);
  25. }
  26. catch (Exception ex)
  27. {
  28. Console.Error.WriteLine(ex);
  29. return Environment.ExitCode = 2;
  30. }
  31. }
  32. public static int Run(params string[] args)
  33. {
  34. bool deleteFile = false;
  35. string tempFile = null;
  36. int result;
  37. bool doHelp = args.Length == 0;
  38. try
  39. {
  40. List<string> protocArgs = new List<string>();
  41. List<string> protoGenArgs = new List<string>();
  42. string protocFile = GuessProtocFile(args);
  43. foreach (string arg in args)
  44. {
  45. doHelp |= StringComparer.OrdinalIgnoreCase.Equals(arg, "/?");
  46. doHelp |= StringComparer.OrdinalIgnoreCase.Equals(arg, "/help");
  47. doHelp |= StringComparer.OrdinalIgnoreCase.Equals(arg, "-?");
  48. doHelp |= StringComparer.OrdinalIgnoreCase.Equals(arg, "-help");
  49. if (arg.StartsWith("--descriptor_set_out="))
  50. {
  51. tempFile = arg.Substring("--descriptor_set_out=".Length);
  52. protoGenArgs.Add(tempFile);
  53. }
  54. }
  55. if (doHelp)
  56. {
  57. Console.WriteLine();
  58. Console.WriteLine("PROTOC.exe: Use any of the following options that begin with '--':");
  59. Console.WriteLine();
  60. try
  61. {
  62. RunProtoc(protocFile, "--help");
  63. }
  64. catch (Exception ex)
  65. {
  66. Console.Error.WriteLine(ex.Message);
  67. }
  68. Console.WriteLine();
  69. Console.WriteLine();
  70. Console.WriteLine(
  71. "PROTOGEN.exe: The following options are used to specify defaults for code generation.");
  72. Console.WriteLine();
  73. Program.Main(new string[0]);
  74. Console.WriteLine();
  75. Console.WriteLine("The following option enables PROTOGEN.exe to find PROTOC.exe");
  76. Console.WriteLine("{0}<directory containing protoc.exe>", ProtocDirectoryArg);
  77. return 0;
  78. }
  79. string pathRoot = Environment.CurrentDirectory;
  80. foreach(string arg in args)
  81. {
  82. if (arg.StartsWith("--proto_path=", StringComparison.InvariantCultureIgnoreCase))
  83. {
  84. pathRoot = arg.Substring(13);
  85. }
  86. }
  87. foreach (string arg in args)
  88. {
  89. if (arg.StartsWith(ProtocDirectoryArg))
  90. {
  91. // Handled earlier
  92. continue;
  93. }
  94. if (arg.StartsWith("--"))
  95. {
  96. protocArgs.Add(arg);
  97. }
  98. else if ((File.Exists(arg) || File.Exists(Path.Combine(pathRoot, arg))) &&
  99. StringComparer.OrdinalIgnoreCase.Equals(".proto", Path.GetExtension(arg)))
  100. {
  101. if (tempFile == null)
  102. {
  103. deleteFile = true;
  104. tempFile = Path.GetTempFileName();
  105. protocArgs.Add(String.Format("--descriptor_set_out={0}", tempFile));
  106. protoGenArgs.Add(tempFile);
  107. }
  108. string patharg = arg;
  109. if (!File.Exists(patharg))
  110. {
  111. patharg = Path.Combine(pathRoot, arg);
  112. }
  113. protocArgs.Add(patharg);
  114. }
  115. else
  116. {
  117. protoGenArgs.Add(arg);
  118. }
  119. }
  120. if (tempFile != null)
  121. {
  122. result = RunProtoc(protocFile, protocArgs.ToArray());
  123. if (result != 0)
  124. {
  125. return result;
  126. }
  127. }
  128. result = Program.Main(protoGenArgs.ToArray());
  129. }
  130. finally
  131. {
  132. if (deleteFile && tempFile != null && File.Exists(tempFile))
  133. {
  134. File.Delete(tempFile);
  135. }
  136. }
  137. return result;
  138. }
  139. /// <summary>
  140. /// Tries to work out where protoc is based on command line arguments, the current
  141. /// directory, the directory containing protogen, and the path.
  142. /// </summary>
  143. /// <returns>The path to protoc.exe, or null if it can't be found.</returns>
  144. private static string GuessProtocFile(params string[] args)
  145. {
  146. // Why oh why is this not in System.IO.Path or Environment...?
  147. List<string> searchPath = new List<string>();
  148. foreach (string arg in args)
  149. {
  150. if (arg.StartsWith("--protoc_dir="))
  151. {
  152. searchPath.Add(arg.Substring(ProtocDirectoryArg.Length));
  153. }
  154. }
  155. searchPath.Add(Environment.CurrentDirectory);
  156. searchPath.Add(AppDomain.CurrentDomain.BaseDirectory);
  157. searchPath.AddRange((Environment.GetEnvironmentVariable("PATH") ?? String.Empty).Split(Path.PathSeparator));
  158. foreach (string path in searchPath)
  159. {
  160. string exeFile = Path.Combine(path, ProtocExecutable);
  161. if (File.Exists(exeFile))
  162. {
  163. return exeFile;
  164. }
  165. }
  166. return null;
  167. }
  168. private static int RunProtoc(string exeFile, params string[] args)
  169. {
  170. if (exeFile == null)
  171. {
  172. throw new FileNotFoundException(
  173. "Unable to locate " + ProtocExecutable +
  174. " make sure it is in the PATH, cwd, or exe dir, or use --protoc_dir=...");
  175. }
  176. ProcessStartInfo psi = new ProcessStartInfo(exeFile);
  177. psi.Arguments = EscapeArguments(args);
  178. psi.RedirectStandardError = true;
  179. psi.RedirectStandardInput = false;
  180. psi.RedirectStandardOutput = true;
  181. psi.ErrorDialog = false;
  182. psi.CreateNoWindow = true;
  183. psi.UseShellExecute = false;
  184. psi.WorkingDirectory = Environment.CurrentDirectory;
  185. Process process = Process.Start(psi);
  186. if (process == null)
  187. {
  188. return 1;
  189. }
  190. process.WaitForExit();
  191. string tmp = process.StandardOutput.ReadToEnd();
  192. if (tmp.Trim().Length > 0)
  193. {
  194. Console.Out.WriteLine(tmp);
  195. }
  196. tmp = process.StandardError.ReadToEnd();
  197. if (tmp.Trim().Length > 0)
  198. {
  199. // Replace protoc output with something more amenable to Visual Studio.
  200. var regexMsvs = new Regex(@"(.*)\((\d+)\).* column=(\d+)\s*:\s*(.*)");
  201. tmp = regexMsvs.Replace(tmp, "$1($2,$3): error CS9999: $4");
  202. var regexGcc = new Regex(@"(.*):(\d+):(\d+):\s*(.*)");
  203. tmp = regexGcc.Replace(tmp, "$1($2,$3): error CS9999: $4");
  204. Console.Error.WriteLine(tmp);
  205. }
  206. return process.ExitCode;
  207. }
  208. /// <summary>
  209. /// Quotes all arguments that contain whitespace, or begin with a quote and returns a single
  210. /// argument string for use with Process.Start().
  211. /// </summary>
  212. /// <remarks>http://csharptest.net/?p=529</remarks>
  213. /// <param name="args">A list of strings for arguments, may not contain null, '\0', '\r', or '\n'</param>
  214. /// <returns>The combined list of escaped/quoted strings</returns>
  215. /// <exception cref="System.ArgumentNullException">Raised when one of the arguments is null</exception>
  216. /// <exception cref="System.ArgumentOutOfRangeException">Raised if an argument contains '\0', '\r', or '\n'</exception>
  217. public static string EscapeArguments(params string[] args)
  218. {
  219. StringBuilder arguments = new StringBuilder();
  220. Regex invalidChar = new Regex("[\x00\x0a\x0d]");// these can not be escaped
  221. Regex needsQuotes = new Regex(@"\s|""");// contains whitespace or two quote characters
  222. Regex escapeQuote = new Regex(@"(\\*)(""|$)");// one or more '\' followed with a quote or end of string
  223. for (int carg = 0; args != null && carg < args.Length; carg++)
  224. {
  225. if (args[carg] == null)
  226. {
  227. throw new ArgumentNullException("args[" + carg + "]");
  228. }
  229. if (invalidChar.IsMatch(args[carg]))
  230. {
  231. throw new ArgumentOutOfRangeException("args[" + carg + "]");
  232. }
  233. if (args[carg] == String.Empty)
  234. {
  235. arguments.Append("\"\"");
  236. }
  237. else if (!needsQuotes.IsMatch(args[carg])) { arguments.Append(args[carg]); }
  238. else
  239. {
  240. arguments.Append('"');
  241. arguments.Append(escapeQuote.Replace(args[carg],
  242. m =>
  243. m.Groups[1].Value + m.Groups[1].Value +
  244. (m.Groups[2].Value == "\"" ? "\\\"" : "")
  245. ));
  246. arguments.Append('"');
  247. }
  248. if (carg + 1 < args.Length)
  249. {
  250. arguments.Append(' ');
  251. }
  252. }
  253. return arguments.ToString();
  254. }
  255. }
  256. }