OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 patch class _WindowsCodePageDecoder { | 5 patch class _WindowsCodePageDecoder { |
6 /* patch */ static String _decodeBytes(List<int> bytes) | 6 /* patch */ static String _decodeBytes(List<int> bytes) |
7 native "SystemEncodingToString"; | 7 native "SystemEncodingToString"; |
8 } | 8 } |
9 | 9 |
10 | 10 |
11 patch class _WindowsCodePageEncoder { | 11 patch class _WindowsCodePageEncoder { |
12 /* patch */ static List<int> _encodeString(String string) | 12 /* patch */ static List<int> _encodeString(String string) |
13 native "StringToSystemEncoding"; | 13 native "StringToSystemEncoding"; |
14 } | 14 } |
15 | 15 |
16 | 16 |
17 patch class Process { | 17 patch class Process { |
18 /* patch */ static Future<Process> start(String executable, | 18 /* patch */ static Future<Process> start(String executable, |
19 List<String> arguments, | 19 List<String> arguments, |
20 [ProcessOptions options]) { | 20 [ProcessOptions options]) { |
21 _ProcessImpl process = new _ProcessImpl(executable, arguments, options); | 21 _ProcessImpl process = new _ProcessImpl(executable, arguments, options); |
22 return process._start(); | 22 return process._start(); |
23 } | 23 } |
24 | 24 |
25 /* patch */ static Future<ProcessResult> run(String executable, | 25 /* patch */ static Future<ProcessResult> run(String executable, |
26 List<String> arguments, | 26 List<String> arguments, |
27 [ProcessOptions options]) { | 27 [ProcessOptions options]) { |
28 return new _NonInteractiveProcess(executable, arguments, options)._result; | 28 return _runNonInteractiveProcess(executable, arguments, options); |
29 } | 29 } |
30 } | 30 } |
31 | 31 |
32 | 32 |
33 patch class _ProcessUtils { | 33 patch class _ProcessUtils { |
34 /* patch */ static _exit(int status) native "Process_Exit"; | 34 /* patch */ static _exit(int status) native "Process_Exit"; |
35 /* patch */ static _setExitCode(int status) native "Process_SetExitCode"; | 35 /* patch */ static _setExitCode(int status) native "Process_SetExitCode"; |
36 } | 36 } |
37 | 37 |
38 | 38 |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
81 _environment = []; | 81 _environment = []; |
82 env.forEach((key, value) { | 82 env.forEach((key, value) { |
83 if (key is !String || value is !String) { | 83 if (key is !String || value is !String) { |
84 throw new ArgumentError( | 84 throw new ArgumentError( |
85 "Environment key or value is not a string: ($key, $value)"); | 85 "Environment key or value is not a string: ($key, $value)"); |
86 } | 86 } |
87 _environment.add('$key=$value'); | 87 _environment.add('$key=$value'); |
88 }); | 88 }); |
89 } | 89 } |
90 | 90 |
91 _in = new _Socket._internalReadOnly(); // stdout coming from process. | 91 // stdin going to process. |
92 _out = new _Socket._internalWriteOnly(); // stdin going to process. | 92 _stdin = new _Socket._writePipe(); |
93 _err = new _Socket._internalReadOnly(); // stderr coming from process. | 93 // stdout coming from process. |
94 _exitHandler = new _Socket._internalReadOnly(); | 94 _stdout = new _Socket._readPipe(); |
| 95 // stderr coming from process. |
| 96 _stderr = new _Socket._readPipe(); |
| 97 _exitHandler = new _Socket._readPipe(); |
95 _ended = false; | 98 _ended = false; |
96 _started = false; | 99 _started = false; |
97 _onExit = null; | |
98 } | 100 } |
99 | 101 |
100 String _windowsArgumentEscape(String argument) { | 102 String _windowsArgumentEscape(String argument) { |
101 var result = argument; | 103 var result = argument; |
102 if (argument.contains('\t') || argument.contains(' ')) { | 104 if (argument.contains('\t') || argument.contains(' ')) { |
103 // Produce something that the C runtime on Windows will parse | 105 // Produce something that the C runtime on Windows will parse |
104 // back as this string. | 106 // back as this string. |
105 | 107 |
106 // Replace any number of '\' followed by '"' with | 108 // Replace any number of '\' followed by '"' with |
107 // twice as many '\' followed by '\"'. | 109 // twice as many '\' followed by '\"'. |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
153 Future<Process> _start() { | 155 Future<Process> _start() { |
154 var completer = new Completer(); | 156 var completer = new Completer(); |
155 // TODO(ager): Make the actual process starting really async instead of | 157 // TODO(ager): Make the actual process starting really async instead of |
156 // simulating it with a timer. | 158 // simulating it with a timer. |
157 new Timer(0, (_) { | 159 new Timer(0, (_) { |
158 var status = new _ProcessStartStatus(); | 160 var status = new _ProcessStartStatus(); |
159 bool success = _startNative(_path, | 161 bool success = _startNative(_path, |
160 _arguments, | 162 _arguments, |
161 _workingDirectory, | 163 _workingDirectory, |
162 _environment, | 164 _environment, |
163 _in, | 165 _stdin._nativeSocket, |
164 _out, | 166 _stdout._nativeSocket, |
165 _err, | 167 _stderr._nativeSocket, |
166 _exitHandler, | 168 _exitHandler._nativeSocket, |
167 status); | 169 status); |
168 if (!success) { | 170 if (!success) { |
169 _in.close(); | |
170 _out.close(); | |
171 _err.close(); | |
172 _exitHandler.close(); | |
173 completer.completeError( | 171 completer.completeError( |
174 new ProcessException(_path, | 172 new ProcessException(_path, |
175 _arguments, | 173 _arguments, |
176 status._errorMessage, | 174 status._errorMessage, |
177 status._errorCode)); | 175 status._errorCode)); |
178 return; | 176 return; |
179 } | 177 } |
180 _started = true; | 178 _started = true; |
181 | 179 |
182 _in._closed = false; | |
183 _out._closed = false; | |
184 _err._closed = false; | |
185 _exitHandler._closed = false; | |
186 | |
187 // Make sure to activate socket handlers now that the file | |
188 // descriptors have been set. | |
189 _in._activateHandlers(); | |
190 _out._activateHandlers(); | |
191 _err._activateHandlers(); | |
192 | |
193 // Setup an exit handler to handle internal cleanup and possible | 180 // Setup an exit handler to handle internal cleanup and possible |
194 // callback when a process terminates. | 181 // callback when a process terminates. |
195 int exitDataRead = 0; | 182 int exitDataRead = 0; |
196 final int EXIT_DATA_SIZE = 8; | 183 final int EXIT_DATA_SIZE = 8; |
197 List<int> exitDataBuffer = new List<int>.fixedLength(EXIT_DATA_SIZE); | 184 List<int> exitDataBuffer = new List<int>.fixedLength(EXIT_DATA_SIZE); |
198 _exitHandler.inputStream.onData = () { | 185 _exitHandler.listen((data) { |
199 | 186 |
200 int exitCode(List<int> ints) { | 187 int exitCode(List<int> ints) { |
201 var code = _intFromBytes(ints, 0); | 188 var code = _intFromBytes(ints, 0); |
202 var negative = _intFromBytes(ints, 4); | 189 var negative = _intFromBytes(ints, 4); |
203 assert(negative == 0 || negative == 1); | 190 assert(negative == 0 || negative == 1); |
204 return (negative == 0) ? code : -code; | 191 return (negative == 0) ? code : -code; |
205 } | 192 } |
206 | 193 |
207 void handleExit() { | 194 void handleExit() { |
208 _ended = true; | 195 _ended = true; |
209 _exitCode = exitCode(exitDataBuffer); | 196 _exitCode.complete(exitCode(exitDataBuffer)); |
210 if (_onExit != null) _onExit(_exitCode); | 197 // Kill stdin, helping hand if the user forgot to do it. |
211 _out.close(); | 198 _stdin.destroy(); |
212 } | 199 } |
213 | 200 |
214 exitDataRead += _exitHandler.inputStream.readInto( | 201 exitDataBuffer.setRange(exitDataRead, data.length, data); |
215 exitDataBuffer, exitDataRead, EXIT_DATA_SIZE - exitDataRead); | 202 exitDataRead += data.length; |
216 if (exitDataRead == EXIT_DATA_SIZE) { | 203 if (exitDataRead == EXIT_DATA_SIZE) { |
217 _exitHandler.close(); | |
218 handleExit(); | 204 handleExit(); |
219 } | 205 } |
220 }; | 206 }); |
221 | 207 |
222 completer.complete(this); | 208 completer.complete(this); |
223 }); | 209 }); |
224 return completer.future; | 210 return completer.future; |
225 } | 211 } |
226 | 212 |
227 bool _startNative(String path, | 213 bool _startNative(String path, |
228 List<String> arguments, | 214 List<String> arguments, |
229 String workingDirectory, | 215 String workingDirectory, |
230 List<String> environment, | 216 List<String> environment, |
231 Socket input, | 217 _NativeSocket stdin, |
232 Socket output, | 218 _NativeSocket stdout, |
233 Socket error, | 219 _NativeSocket stderr, |
234 Socket exitHandler, | 220 _NativeSocket exitHandler, |
235 _ProcessStartStatus status) native "Process_Start"; | 221 _ProcessStartStatus status) native "Process_Start"; |
236 | 222 |
237 InputStream get stdout { | 223 Stream<List<int>> get stdout { |
238 return _in.inputStream; | 224 // TODO(ajohnsen): Get stream object only. |
| 225 return _stdout; |
239 } | 226 } |
240 | 227 |
241 InputStream get stderr { | 228 Stream<List<int>> get stderr { |
242 return _err.inputStream; | 229 // TODO(ajohnsen): Get stream object only. |
| 230 return _stderr; |
243 } | 231 } |
244 | 232 |
245 OutputStream get stdin { | 233 IOSink get stdin { |
246 return _out.outputStream; | 234 // TODO(ajohnsen): Get consumer object only. |
| 235 return _stdin; |
247 } | 236 } |
248 | 237 |
| 238 Future<int> get exitCode => _exitCode.future; |
| 239 |
249 bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) { | 240 bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) { |
250 if (signal is! ProcessSignal) { | 241 if (signal is! ProcessSignal) { |
251 throw new ArgumentError( | 242 throw new ArgumentError( |
252 "Argument 'signal' must be a ProcessSignal"); | 243 "Argument 'signal' must be a ProcessSignal"); |
253 } | 244 } |
254 assert(_started); | 245 assert(_started); |
255 if (_ended) return false; | 246 if (_ended) return false; |
256 return _kill(this, signal._signalNumber); | 247 return _kill(this, signal._signalNumber); |
257 } | 248 } |
258 | 249 |
259 bool _kill(Process p, int signal) native "Process_Kill"; | 250 bool _kill(Process p, int signal) native "Process_Kill"; |
260 | 251 |
261 void set onExit(void callback(int exitCode)) { | |
262 if (_ended) callback(_exitCode); | |
263 _onExit = callback; | |
264 } | |
265 | |
266 String _path; | 252 String _path; |
267 List<String> _arguments; | 253 List<String> _arguments; |
268 String _workingDirectory; | 254 String _workingDirectory; |
269 List<String> _environment; | 255 List<String> _environment; |
270 // Private methods of _Socket are used by _in, _out, and _err. | 256 // Private methods of Socket are used by _in, _out, and _err. |
271 _Socket _in; | 257 Socket _stdin; |
272 _Socket _out; | 258 Socket _stdout; |
273 _Socket _err; | 259 Socket _stderr; |
274 Socket _exitHandler; | 260 Socket _exitHandler; |
275 int _exitCode; | |
276 bool _ended; | 261 bool _ended; |
277 bool _started; | 262 bool _started; |
278 Function _onExit; | 263 final Completer<int> _exitCode = new Completer<int>(); |
279 } | 264 } |
280 | 265 |
281 | 266 |
282 // _NonInteractiveProcess is a wrapper around an interactive process | 267 // _NonInteractiveProcess is a wrapper around an interactive process |
283 // that buffers output so it can be delivered when the process exits. | 268 // that buffers output so it can be delivered when the process exits. |
284 // _NonInteractiveProcess is used to implement the Process.run | 269 // _NonInteractiveProcess is used to implement the Process.run |
285 // method. | 270 // method. |
286 class _NonInteractiveProcess { | 271 Future<ProcessResult> _runNonInteractiveProcess(String path, |
287 _NonInteractiveProcess(String path, | 272 List<String> arguments, |
288 List<String> arguments, | 273 ProcessOptions options) { |
289 ProcessOptions options) { | 274 // Extract output encoding options and verify arguments. |
290 _completer = new Completer<ProcessResult>(); | 275 var stdoutEncoding = Encoding.SYSTEM; |
291 // Extract output encoding options and verify arguments. | 276 var stderrEncoding = Encoding.SYSTEM; |
292 var stdoutEncoding = Encoding.SYSTEM; | 277 if (options != null) { |
293 var stderrEncoding = Encoding.SYSTEM; | 278 if (options.stdoutEncoding != null) { |
294 if (options != null) { | 279 stdoutEncoding = options.stdoutEncoding; |
295 if (options.stdoutEncoding != null) { | 280 if (stdoutEncoding is !Encoding) { |
296 stdoutEncoding = options.stdoutEncoding; | 281 throw new ArgumentError( |
297 if (stdoutEncoding is !Encoding) { | 282 'stdoutEncoding option is not an encoding: $stdoutEncoding'); |
298 throw new ArgumentError( | |
299 'stdoutEncoding option is not an encoding: $stdoutEncoding'); | |
300 } | |
301 } | |
302 if (options.stderrEncoding != null) { | |
303 stderrEncoding = options.stderrEncoding; | |
304 if (stderrEncoding is !Encoding) { | |
305 throw new ArgumentError( | |
306 'stderrEncoding option is not an encoding: $stderrEncoding'); | |
307 } | |
308 } | 283 } |
309 } | 284 } |
310 | 285 if (options.stderrEncoding != null) { |
311 // Start the underlying process. | 286 stderrEncoding = options.stderrEncoding; |
312 var processFuture = new _ProcessImpl(path, arguments, options)._start(); | 287 if (stderrEncoding is !Encoding) { |
313 | 288 throw new ArgumentError( |
314 processFuture.then((Process p) { | 289 'stderrEncoding option is not an encoding: $stderrEncoding'); |
315 // Make sure the process stdin is closed. | 290 } |
316 p.stdin.close(); | |
317 | |
318 // Setup process exit handling. | |
319 p.onExit = (exitCode) { | |
320 _exitCode = exitCode; | |
321 _checkDone(); | |
322 }; | |
323 | |
324 // Setup stdout handling. | |
325 _stdoutBuffer = new StringBuffer(); | |
326 var stdoutStream = new StringInputStream(p.stdout, stdoutEncoding); | |
327 stdoutStream.onData = () { | |
328 var data = stdoutStream.read(); | |
329 if (data != null) _stdoutBuffer.add(data); | |
330 }; | |
331 stdoutStream.onClosed = () { | |
332 _stdoutClosed = true; | |
333 _checkDone(); | |
334 }; | |
335 | |
336 // Setup stderr handling. | |
337 _stderrBuffer = new StringBuffer(); | |
338 var stderrStream = new StringInputStream(p.stderr, stderrEncoding); | |
339 stderrStream.onData = () { | |
340 var data = stderrStream.read(); | |
341 if (data != null) _stderrBuffer.add(data); | |
342 }; | |
343 stderrStream.onClosed = () { | |
344 _stderrClosed = true; | |
345 _checkDone(); | |
346 }; | |
347 }).catchError((error) { | |
348 _completer.completeError(error.error); | |
349 }); | |
350 } | |
351 | |
352 void _checkDone() { | |
353 if (_exitCode != null && _stderrClosed && _stdoutClosed) { | |
354 _completer.complete(new _ProcessResult(_exitCode, | |
355 _stdoutBuffer.toString(), | |
356 _stderrBuffer.toString())); | |
357 } | 291 } |
358 } | 292 } |
359 | 293 |
360 Future<ProcessResult> get _result => _completer.future; | 294 // Start the underlying process. |
| 295 return Process.start(path, arguments, options).then((Process p) { |
| 296 // Make sure the process stdin is closed. |
| 297 p.stdin.close(); |
361 | 298 |
362 Completer<ProcessResult> _completer; | 299 // Setup stdout handling. |
363 StringBuffer _stdoutBuffer; | 300 Future<StringBuffer> stdout = p.stdout |
364 StringBuffer _stderrBuffer; | 301 .transform(new StringDecoder(stdoutEncoding)) |
365 int _exitCode; | 302 .reduce( |
366 bool _stdoutClosed = false; | 303 new StringBuffer(), |
367 bool _stderrClosed = false; | 304 (buf, data) { |
| 305 buf.add(data); |
| 306 return buf; |
| 307 }); |
| 308 |
| 309 Future<StringBuffer> stderr = p.stderr |
| 310 .transform(new StringDecoder(stderrEncoding)) |
| 311 .reduce( |
| 312 new StringBuffer(), |
| 313 (buf, data) { |
| 314 buf.add(data); |
| 315 return buf; |
| 316 }); |
| 317 |
| 318 return Future.wait([p.exitCode, stdout, stderr]).then((result) { |
| 319 return new _ProcessResult(result[0], |
| 320 result[1].toString(), |
| 321 result[2].toString()); |
| 322 }); |
| 323 }); |
368 } | 324 } |
369 | 325 |
370 | 326 |
371 class _ProcessResult implements ProcessResult { | 327 class _ProcessResult implements ProcessResult { |
372 const _ProcessResult(int this.exitCode, | 328 const _ProcessResult(int this.exitCode, |
373 String this.stdout, | 329 String this.stdout, |
374 String this.stderr); | 330 String this.stderr); |
375 | 331 |
376 final int exitCode; | 332 final int exitCode; |
377 final String stdout; | 333 final String stdout; |
378 final String stderr; | 334 final String stderr; |
379 } | 335 } |
OLD | NEW |