| 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 |