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 |
|
---|
8 | namespace Google.ProtocolBuffers.ProtoGen
|
---|
9 | {
|
---|
10 | /// <summary>
|
---|
11 | /// Preprocesses any input files with an extension of '.proto' by running protoc.exe. If arguments
|
---|
12 | /// are supplied with '--' prefix they are provided to protoc.exe, otherwise they are assumed to
|
---|
13 | /// be used for ProtoGen.exe which is run on the resulting output proto buffer. If the option
|
---|
14 | /// --descriptor_set_out= is specified the proto buffer file is kept, otherwise it will be removed
|
---|
15 | /// after code generation.
|
---|
16 | /// </summary>
|
---|
17 | public class ProgramPreprocess
|
---|
18 | {
|
---|
19 | private const string ProtocExecutable = "protoc.exe";
|
---|
20 | private const string ProtocDirectoryArg = "--protoc_dir=";
|
---|
21 |
|
---|
22 | private static int Main(string[] args)
|
---|
23 | {
|
---|
24 | try
|
---|
25 | {
|
---|
26 | return Environment.ExitCode = Run(args);
|
---|
27 | }
|
---|
28 | catch (Exception ex)
|
---|
29 | {
|
---|
30 | Console.Error.WriteLine(ex);
|
---|
31 | return Environment.ExitCode = 2;
|
---|
32 | }
|
---|
33 | }
|
---|
34 |
|
---|
35 | public static int Run(params string[] args)
|
---|
36 | {
|
---|
37 | bool deleteFile = false;
|
---|
38 | string tempFile = null;
|
---|
39 | int result;
|
---|
40 | bool doHelp = args.Length == 0;
|
---|
41 | try
|
---|
42 | {
|
---|
43 | List<string> protocArgs = new List<string>();
|
---|
44 | List<string> protoGenArgs = new List<string>();
|
---|
45 |
|
---|
46 | string protocFile = GuessProtocFile(args);
|
---|
47 |
|
---|
48 | foreach (string arg in args)
|
---|
49 | {
|
---|
50 | doHelp |= StringComparer.OrdinalIgnoreCase.Equals(arg, "/?");
|
---|
51 | doHelp |= StringComparer.OrdinalIgnoreCase.Equals(arg, "/help");
|
---|
52 | doHelp |= StringComparer.OrdinalIgnoreCase.Equals(arg, "-?");
|
---|
53 | doHelp |= StringComparer.OrdinalIgnoreCase.Equals(arg, "-help");
|
---|
54 |
|
---|
55 | if (arg.StartsWith("--descriptor_set_out="))
|
---|
56 | {
|
---|
57 | tempFile = arg.Substring("--descriptor_set_out=".Length);
|
---|
58 | protoGenArgs.Add(tempFile);
|
---|
59 | }
|
---|
60 | }
|
---|
61 |
|
---|
62 | if (doHelp)
|
---|
63 | {
|
---|
64 | Console.WriteLine();
|
---|
65 | Console.WriteLine("PROTOC.exe: Use any of the following options that begin with '--':");
|
---|
66 | Console.WriteLine();
|
---|
67 | try
|
---|
68 | {
|
---|
69 | RunProtoc(protocFile, "--help");
|
---|
70 | }
|
---|
71 | catch (Exception ex)
|
---|
72 | {
|
---|
73 | Console.Error.WriteLine(ex.Message);
|
---|
74 | }
|
---|
75 | Console.WriteLine();
|
---|
76 | Console.WriteLine();
|
---|
77 | Console.WriteLine(
|
---|
78 | "PROTOGEN.exe: The following options are used to specify defaults for code generation.");
|
---|
79 | Console.WriteLine();
|
---|
80 | Program.Main(new string[0]);
|
---|
81 | Console.WriteLine();
|
---|
82 | Console.WriteLine("The following option enables PROTOGEN.exe to find PROTOC.exe");
|
---|
83 | Console.WriteLine("{0}<directory containing protoc.exe>", ProtocDirectoryArg);
|
---|
84 | return 0;
|
---|
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) &&
|
---|
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 | protocArgs.Add(arg);
|
---|
109 | }
|
---|
110 | else
|
---|
111 | {
|
---|
112 | protoGenArgs.Add(arg);
|
---|
113 | }
|
---|
114 | }
|
---|
115 |
|
---|
116 | if (tempFile != null)
|
---|
117 | {
|
---|
118 | result = RunProtoc(protocFile, protocArgs.ToArray());
|
---|
119 | if (result != 0)
|
---|
120 | {
|
---|
121 | return result;
|
---|
122 | }
|
---|
123 | }
|
---|
124 |
|
---|
125 | result = Program.Main(protoGenArgs.ToArray());
|
---|
126 | }
|
---|
127 | finally
|
---|
128 | {
|
---|
129 | if (deleteFile && tempFile != null && File.Exists(tempFile))
|
---|
130 | {
|
---|
131 | File.Delete(tempFile);
|
---|
132 | }
|
---|
133 | }
|
---|
134 | return result;
|
---|
135 | }
|
---|
136 |
|
---|
137 | /// <summary>
|
---|
138 | /// Tries to work out where protoc is based on command line arguments, the current
|
---|
139 | /// directory, the directory containing protogen, and the path.
|
---|
140 | /// </summary>
|
---|
141 | /// <returns>The path to protoc.exe, or null if it can't be found.</returns>
|
---|
142 | private static string GuessProtocFile(params string[] args)
|
---|
143 | {
|
---|
144 | // Why oh why is this not in System.IO.Path or Environment...?
|
---|
145 | List<string> searchPath = new List<string>();
|
---|
146 | foreach (string arg in args)
|
---|
147 | {
|
---|
148 | if (arg.StartsWith("--protoc_dir="))
|
---|
149 | {
|
---|
150 | searchPath.Add(arg.Substring(ProtocDirectoryArg.Length));
|
---|
151 | }
|
---|
152 | }
|
---|
153 | searchPath.Add(Environment.CurrentDirectory);
|
---|
154 | searchPath.Add(AppDomain.CurrentDomain.BaseDirectory);
|
---|
155 | searchPath.AddRange((Environment.GetEnvironmentVariable("PATH") ?? String.Empty).Split(Path.PathSeparator));
|
---|
156 |
|
---|
157 | foreach (string path in searchPath)
|
---|
158 | {
|
---|
159 | string exeFile = Path.Combine(path, ProtocExecutable);
|
---|
160 | if (File.Exists(exeFile))
|
---|
161 | {
|
---|
162 | return exeFile;
|
---|
163 | }
|
---|
164 | }
|
---|
165 | return null;
|
---|
166 | }
|
---|
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 |
|
---|
177 | ProcessStartInfo psi = new ProcessStartInfo(exeFile);
|
---|
178 | psi.Arguments = EscapeArguments(args);
|
---|
179 | psi.RedirectStandardError = true;
|
---|
180 | psi.RedirectStandardInput = false;
|
---|
181 | psi.RedirectStandardOutput = true;
|
---|
182 | psi.ErrorDialog = false;
|
---|
183 | psi.CreateNoWindow = true;
|
---|
184 | psi.UseShellExecute = false;
|
---|
185 | psi.WorkingDirectory = Environment.CurrentDirectory;
|
---|
186 |
|
---|
187 | Process process = Process.Start(psi);
|
---|
188 | if (process == null)
|
---|
189 | {
|
---|
190 | return 1;
|
---|
191 | }
|
---|
192 |
|
---|
193 | process.WaitForExit();
|
---|
194 |
|
---|
195 | string tmp = process.StandardOutput.ReadToEnd();
|
---|
196 | if (tmp.Trim().Length > 0)
|
---|
197 | {
|
---|
198 | Console.Out.WriteLine(tmp);
|
---|
199 | }
|
---|
200 | tmp = process.StandardError.ReadToEnd();
|
---|
201 | if (tmp.Trim().Length > 0)
|
---|
202 | {
|
---|
203 | Console.Error.WriteLine(tmp);
|
---|
204 | }
|
---|
205 | return process.ExitCode;
|
---|
206 | }
|
---|
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 | } |
---|