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 /** | 5 /** |
6 * Helper functionality to make working with IO easier. | 6 * Helper functionality to make working with IO easier. |
7 */ | 7 */ |
8 library io; | 8 library io; |
9 | 9 |
10 import 'dart:io'; | 10 import 'dart:io'; |
(...skipping 210 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
221 * [Directory]. Returns a [Future] that completes when the deletion is done. | 221 * [Directory]. Returns a [Future] that completes when the deletion is done. |
222 */ | 222 */ |
223 Future<Directory> deleteDir(dir) { | 223 Future<Directory> deleteDir(dir) { |
224 dir = _getDirectory(dir); | 224 dir = _getDirectory(dir); |
225 return dir.delete(recursive: true); | 225 return dir.delete(recursive: true); |
226 } | 226 } |
227 | 227 |
228 /** | 228 /** |
229 * Asynchronously lists the contents of [dir], which can be a [String] directory | 229 * Asynchronously lists the contents of [dir], which can be a [String] directory |
230 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents | 230 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents |
231 * (defaults to `false`). If [includeSpecialFiles] is `true`, includes | 231 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files |
232 * hidden `.DS_Store` files (defaults to `false`, other hidden files may be | 232 * beginning with `.` (defaults to `false`). |
233 * omitted later). | |
234 */ | 233 */ |
235 Future<List<String>> listDir(dir, | 234 Future<List<String>> listDir(dir, |
236 [bool recursive = false, bool includeSpecialFiles = false]) { | 235 {bool recursive: false, bool includeHiddenFiles: false}) { |
237 final completer = new Completer<List<String>>(); | 236 final completer = new Completer<List<String>>(); |
238 final contents = <String>[]; | 237 final contents = <String>[]; |
239 | 238 |
240 dir = _getDirectory(dir); | 239 dir = _getDirectory(dir); |
241 var lister = dir.list(recursive: recursive); | 240 var lister = dir.list(recursive: recursive); |
242 | 241 |
243 lister.onDone = (done) { | 242 lister.onDone = (done) { |
244 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile | 243 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile |
245 // aren't guaranteed to be called in a certain order. So far, they seem to. | 244 // aren't guaranteed to be called in a certain order. So far, they seem to. |
246 if (done) completer.complete(contents); | 245 if (done) completer.complete(contents); |
247 }; | 246 }; |
248 | 247 |
249 lister.onError = (error) => completer.completeException(error); | 248 lister.onError = (error) => completer.completeException(error); |
250 lister.onDir = (file) => contents.add(file); | 249 lister.onDir = (file) => contents.add(file); |
251 lister.onFile = (file) { | 250 lister.onFile = (file) { |
252 if (!includeSpecialFiles) { | 251 if (!includeHiddenFiles && basename(file).startsWith('.')) return; |
253 if (basename(file) == '.DS_Store') return; | |
254 } | |
255 contents.add(file); | 252 contents.add(file); |
256 }; | 253 }; |
257 | 254 |
258 return completer.future; | 255 return completer.future; |
259 } | 256 } |
260 | 257 |
261 /** | 258 /** |
262 * Asynchronously determines if [dir], which can be a [String] directory path | 259 * Asynchronously determines if [dir], which can be a [String] directory path |
263 * or a [Directory], exists on the file system. Returns a [Future] that | 260 * or a [Directory], exists on the file system. Returns a [Future] that |
264 * completes with the result. | 261 * completes with the result. |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
365 .toNativePath(); | 362 .toNativePath(); |
366 } | 363 } |
367 | 364 |
368 /// Resolves [path] relative to the location of pub.dart. | 365 /// Resolves [path] relative to the location of pub.dart. |
369 String relativeToPub(String path) { | 366 String relativeToPub(String path) { |
370 var scriptPath = new File(new Options().script).fullPathSync(); | 367 var scriptPath = new File(new Options().script).fullPathSync(); |
371 var scriptDir = new Path.fromNative(scriptPath).directoryPath; | 368 var scriptDir = new Path.fromNative(scriptPath).directoryPath; |
372 return scriptDir.append(path).canonicalize().toNativePath(); | 369 return scriptDir.append(path).canonicalize().toNativePath(); |
373 } | 370 } |
374 | 371 |
372 /// A StringInputStream reading from stdin. | |
373 StringInputStream _stringStdin = new StringInputStream(stdin); | |
Bob Nystrom
2012/11/26 23:39:52
"StringInputStream" -> "final".
nweiz
2012/11/27 20:15:54
Done.
| |
374 | |
375 /// Returns a single line read from a [StringInputStream]. By default, reads | |
376 /// from stdin. | |
377 /// | |
378 /// A [StringInputStream] passed to this should have no callbacks registered. | |
379 Future<String> readLine([StringInputStream stream]) { | |
380 if (stream == null) stream = _stringStdin; | |
381 if (stream.closed) return new Future.immediate(''); | |
382 void removeCallbacks() { | |
383 stream.onClosed = null; | |
384 stream.onLine = null; | |
385 stream.onError = null; | |
386 } | |
387 | |
388 var completer = new Completer(); | |
389 stream.onClosed = () { | |
390 removeCallbacks(); | |
391 completer.complete(''); | |
392 }; | |
393 | |
394 stream.onLine = () { | |
395 removeCallbacks(); | |
396 completer.complete(stream.readLine()); | |
397 }; | |
398 | |
399 stream.onError = (e) { | |
400 removeCallbacks(); | |
401 completer.completeException(e); | |
402 }; | |
403 | |
404 return completer.future; | |
405 } | |
406 | |
375 // TODO(nweiz): make this configurable | 407 // TODO(nweiz): make this configurable |
376 /** | 408 /** |
377 * The amount of time in milliseconds to allow HTTP requests before assuming | 409 * The amount of time in milliseconds to allow HTTP requests before assuming |
378 * they've failed. | 410 * they've failed. |
379 */ | 411 */ |
380 final HTTP_TIMEOUT = 30 * 1000; | 412 final HTTP_TIMEOUT = 30 * 1000; |
381 | 413 |
382 /** | 414 /** |
383 * Opens an input stream for a HTTP GET request to [uri], which may be a | 415 * Opens an input stream for a HTTP GET request to [uri], which may be a |
384 * [String] or [Uri]. | 416 * [String] or [Uri]. |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
425 return completer.future; | 457 return completer.future; |
426 } | 458 } |
427 | 459 |
428 /** | 460 /** |
429 * Opens an input stream for a HTTP GET request to [uri], which may be a | 461 * Opens an input stream for a HTTP GET request to [uri], which may be a |
430 * [String] or [Uri]. Completes with the result of the request as a String. | 462 * [String] or [Uri]. Completes with the result of the request as a String. |
431 */ | 463 */ |
432 Future<String> httpGetString(uri) { | 464 Future<String> httpGetString(uri) { |
433 var future = httpGet(uri).chain((stream) => consumeInputStream(stream)) | 465 var future = httpGet(uri).chain((stream) => consumeInputStream(stream)) |
434 .transform((bytes) => new String.fromCharCodes(bytes)); | 466 .transform((bytes) => new String.fromCharCodes(bytes)); |
435 return timeout(future, HTTP_TIMEOUT, 'Timed out while fetching URL "$uri".'); | 467 return timeout(future, HTTP_TIMEOUT, 'fetching URL "$uri"'); |
436 } | 468 } |
437 | 469 |
438 /** | 470 /** |
439 * Takes all input from [source] and writes it to [sink]. | 471 * Takes all input from [source] and writes it to [sink]. |
440 * | 472 * |
441 * [onClosed] is called when [source] is closed. | 473 * [onClosed] is called when [source] is closed. |
442 */ | 474 */ |
443 void pipeInputToInput(InputStream source, ListInputStream sink, | 475 void pipeInputToInput(InputStream source, ListInputStream sink, |
444 [void onClosed()]) { | 476 [void onClosed()]) { |
445 source.onClosed = () { | 477 source.onClosed = () { |
446 sink.markEndOfStream(); | 478 sink.markEndOfStream(); |
447 if (onClosed != null) onClosed(); | 479 if (onClosed != null) onClosed(); |
448 }; | 480 }; |
449 source.onData = () => sink.write(source.read()); | 481 source.onData = () => sink.write(source.read()); |
450 // TODO(nweiz): propagate this error to the sink. See issue 3657. | 482 // TODO(nweiz): propagate this error to the sink. See issue 3657. |
451 source.onError = (e) { throw e; }; | 483 source.onError = (e) { throw e; }; |
452 } | 484 } |
453 | 485 |
454 /** | 486 /** |
455 * Buffers all input from an InputStream and returns it as a future. | 487 * Buffers all input from an InputStream and returns it as a future. |
456 */ | 488 */ |
457 Future<List<int>> consumeInputStream(InputStream stream) { | 489 Future<List<int>> consumeInputStream(InputStream stream) { |
490 if (stream.closed) return new Future.immediate(<int>[]); | |
491 | |
458 var completer = new Completer<List<int>>(); | 492 var completer = new Completer<List<int>>(); |
459 var buffer = <int>[]; | 493 var buffer = <int>[]; |
460 stream.onClosed = () => completer.complete(buffer); | 494 stream.onClosed = () => completer.complete(buffer); |
461 stream.onData = () => buffer.addAll(stream.read()); | 495 stream.onData = () => buffer.addAll(stream.read()); |
462 stream.onError = (e) => completer.completeException(e); | 496 stream.onError = (e) => completer.completeException(e); |
463 return completer.future; | 497 return completer.future; |
464 } | 498 } |
465 | 499 |
500 /// Buffers all input from a StringInputStream and returns it as a future. | |
501 Future<String> consumeStringInputStream(StringInputStream stream) { | |
502 if (stream.closed) return new Future.immediate(''); | |
503 | |
504 var completer = new Completer<String>(); | |
505 var buffer = new StringBuffer(); | |
506 stream.onClosed = () => completer.complete(buffer.toString()); | |
507 stream.onData = () => buffer.add(stream.read()); | |
508 stream.onError = (e) => completer.completeException(e); | |
509 return completer.future; | |
510 } | |
511 | |
466 /// Spawns and runs the process located at [executable], passing in [args]. | 512 /// Spawns and runs the process located at [executable], passing in [args]. |
467 /// Returns a [Future] that will complete the results of the process after it | 513 /// Returns a [Future] that will complete with the results of the process after |
468 /// has ended. | 514 /// it has ended. |
469 /// | 515 /// |
470 /// The spawned process will inherit its parent's environment variables. If | 516 /// The spawned process will inherit its parent's environment variables. If |
471 /// [environment] is provided, that will be used to augment (not replace) the | 517 /// [environment] is provided, that will be used to augment (not replace) the |
472 /// the inherited variables. | 518 /// the inherited variables. |
519 Future<PubProcessResult> runProcess(String executable, List<String> args, | |
520 {workingDir, Map<String, String> environment}) { | |
521 return _doProcess(Process.run, executable, args, workingDir, environment) | |
522 .transform((result) { | |
523 // TODO(rnystrom): Remove this and change to returning one string. | |
524 List<String> toLines(String output) { | |
525 var lines = output.split(NEWLINE_PATTERN); | |
526 if (!lines.isEmpty && lines.last == "") lines.removeLast(); | |
527 return lines; | |
528 } | |
529 return new PubProcessResult(toLines(result.stdout), | |
530 toLines(result.stderr), | |
531 result.exitCode); | |
532 }); | |
533 } | |
534 | |
535 /// Spawns the process located at [executable], passing in [args]. Returns a | |
536 /// [Future] that will complete with the [Process] once it's been started. | |
473 /// | 537 /// |
474 /// If [pipeStdout] and/or [pipeStderr] are set, all output from the | 538 /// The spawned process will inherit its parent's environment variables. If |
475 /// subprocess's output streams are sent to the parent process's output streams. | 539 /// [environment] is provided, that will be used to augment (not replace) the |
476 /// Output from piped streams won't be available in the result object. | 540 /// the inherited variables. |
477 Future<PubProcessResult> runProcess(String executable, List<String> args, | 541 Future<Process> startProcess(String executable, List<String> args, |
478 {workingDir, Map<String, String> environment, bool pipeStdout: false, | 542 {workingDir, Map<String, String> environment}) => |
479 bool pipeStderr: false}) { | 543 _doProcess(Process.start, executable, args, workingDir, environment); |
480 int exitCode; | |
481 | 544 |
545 /// Calls [fn] with appropriately modified arguments. [fn] should have the same | |
546 /// signature as [Process.start], except that the returned [Future] may have a | |
547 /// type other than [Process]. | |
548 Future _doProcess(Function fn, String executable, List<String> args, workingDir, | |
549 Map<String, String> environment) { | |
482 // TODO(rnystrom): Should dart:io just handle this? | 550 // TODO(rnystrom): Should dart:io just handle this? |
483 // Spawning a process on Windows will not look for the executable in the | 551 // Spawning a process on Windows will not look for the executable in the |
484 // system path. So, if executable looks like it needs that (i.e. it doesn't | 552 // system path. So, if executable looks like it needs that (i.e. it doesn't |
485 // have any path separators in it), then spawn it through a shell. | 553 // have any path separators in it), then spawn it through a shell. |
486 if ((Platform.operatingSystem == "windows") && | 554 if ((Platform.operatingSystem == "windows") && |
487 (executable.indexOf('\\') == -1)) { | 555 (executable.indexOf('\\') == -1)) { |
488 args = flatten(["/c", executable, args]); | 556 args = flatten(["/c", executable, args]); |
489 executable = "cmd"; | 557 executable = "cmd"; |
490 } | 558 } |
491 | 559 |
492 final options = new ProcessOptions(); | 560 final options = new ProcessOptions(); |
493 if (workingDir != null) { | 561 if (workingDir != null) { |
494 options.workingDirectory = _getDirectory(workingDir).path; | 562 options.workingDirectory = _getDirectory(workingDir).path; |
495 } | 563 } |
496 | 564 |
497 if (environment != null) { | 565 if (environment != null) { |
498 options.environment = new Map.from(Platform.environment); | 566 options.environment = new Map.from(Platform.environment); |
499 environment.forEach((key, value) => options.environment[key] = value); | 567 environment.forEach((key, value) => options.environment[key] = value); |
500 } | 568 } |
501 | 569 |
502 var future = Process.run(executable, args, options); | 570 return fn(executable, args, options); |
503 return future.transform((result) { | |
504 // TODO(rnystrom): Remove this and change to returning one string. | |
505 List<String> toLines(String output) { | |
506 var lines = output.split(NEWLINE_PATTERN); | |
507 if (!lines.isEmpty && lines.last == "") lines.removeLast(); | |
508 return lines; | |
509 } | |
510 return new PubProcessResult(toLines(result.stdout), | |
511 toLines(result.stderr), | |
512 result.exitCode); | |
513 }); | |
514 } | 571 } |
515 | 572 |
516 /** | 573 /** |
517 * Wraps [input] to provide a timeout. If [input] completes before | 574 * Wraps [input] to provide a timeout. If [input] completes before |
518 * [milliseconds] have passed, then the return value completes in the same way. | 575 * [milliseconds] have passed, then the return value completes in the same way. |
519 * However, if [milliseconds] pass before [input] has completed, it completes | 576 * However, if [milliseconds] pass before [input] has completed, it completes |
520 * with a [TimeoutException] with [message]. | 577 * with a [TimeoutException] with [description] (which should be a fragment |
578 * describing the action that timed out). | |
521 * | 579 * |
522 * Note that timing out will not cancel the asynchronous operation behind | 580 * Note that timing out will not cancel the asynchronous operation behind |
523 * [input]. | 581 * [input]. |
524 */ | 582 */ |
525 Future timeout(Future input, int milliseconds, String message) { | 583 Future timeout(Future input, int milliseconds, String description) { |
526 var completer = new Completer(); | 584 var completer = new Completer(); |
527 var timer = new Timer(milliseconds, (_) { | 585 var timer = new Timer(milliseconds, (_) { |
528 if (completer.future.isComplete) return; | 586 if (completer.future.isComplete) return; |
529 completer.completeException(new TimeoutException(message)); | 587 completer.completeException(new TimeoutException( |
588 'Timed out while $description.')); | |
530 }); | 589 }); |
531 input.handleException((e) { | 590 input.handleException((e) { |
532 if (completer.future.isComplete) return false; | 591 if (completer.future.isComplete) return false; |
533 timer.cancel(); | 592 timer.cancel(); |
534 completer.completeException(e); | 593 completer.completeException(e); |
535 return true; | 594 return true; |
536 }); | 595 }); |
537 input.then((value) { | 596 input.then((value) { |
538 if (completer.future.isComplete) return; | 597 if (completer.future.isComplete) return; |
539 timer.cancel(); | 598 timer.cancel(); |
(...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
697 '${Strings.join(result.stdout, "\n")}\n' | 756 '${Strings.join(result.stdout, "\n")}\n' |
698 '${Strings.join(result.stderr, "\n")}'; | 757 '${Strings.join(result.stderr, "\n")}'; |
699 } | 758 } |
700 | 759 |
701 // Clean up the temp directory. | 760 // Clean up the temp directory. |
702 // TODO(rnystrom): Should also delete this if anything fails. | 761 // TODO(rnystrom): Should also delete this if anything fails. |
703 return deleteDir(tempDir); | 762 return deleteDir(tempDir); |
704 }).transform((_) => true); | 763 }).transform((_) => true); |
705 } | 764 } |
706 | 765 |
766 /// Create a .tar.gz archive from a list of entries. Each entry can be a | |
767 /// [String], [Directory], or [File] object. Returns an [InputStream] that will | |
768 /// emit the contents of the archive. | |
769 InputStream createTarGz(List contents) { | |
770 // TODO(nweiz): Propagate errors to the returned stream (including non-zero | |
771 // exit codes). See issue 3657. | |
772 var stream = new ListInputStream(); | |
773 | |
774 if (Platform.operatingSystem != "windows") { | |
775 var args = ["--create", "--gzip"]; | |
776 args.addAll(contents.map(_getPath)); | |
Bob Nystrom
2012/11/26 23:39:52
Do we need to worry about blowing the arg buffer h
nweiz
2012/11/27 20:15:54
I think the buffer is really big on modern systems
| |
777 startProcess("tar", args).then((process) { | |
778 pipeInputToInput(process.stdout, stream); | |
779 process.stderr.pipe(stderr, close: false); | |
780 }); | |
781 return stream; | |
782 } | |
783 | |
784 withTempDir((tempDir) { | |
Bob Nystrom
2012/11/26 23:39:52
Yay Windows support!
nweiz
2012/11/27 20:15:54
I just more or less copied your code from test_pub
| |
785 // Create the tar file. | |
786 var tarFile = join(tempDir, "intermediate.tar"); | |
787 var args = ["a", tarFile]; | |
788 args.addAll(contents.map((entry) => '-i!"$entry"')); | |
789 | |
790 // Note: This line of code gets munged by create_sdk.py to be the correct | |
791 // relative path to 7zip in the SDK. | |
792 var pathTo7zip = '../../third_party/7zip/7za.exe'; | |
793 var command = relativeToPub(pathTo7zip); | |
794 | |
795 return runProcess(command, args).chain((_) { | |
796 // GZIP it. 7zip doesn't support doing both as a single operation. Send | |
797 // the output to stdout. | |
798 args = ["a", "not used", "-so", tarFile]; | |
799 return startProcess(command, args); | |
800 }).transform((process) { | |
801 pipeInputToInput(process.stdout, stream); | |
802 process.stderr.pipe(stderr, close: false); | |
803 }); | |
804 }); | |
805 return stream; | |
806 } | |
807 | |
707 /** | 808 /** |
708 * Exception thrown when an HTTP operation fails. | 809 * Exception thrown when an HTTP operation fails. |
709 */ | 810 */ |
710 class PubHttpException implements Exception { | 811 class PubHttpException implements Exception { |
711 final int statusCode; | 812 final int statusCode; |
712 final String reason; | 813 final String reason; |
713 | 814 |
714 const PubHttpException(this.statusCode, this.reason); | 815 const PubHttpException(this.statusCode, this.reason); |
715 | 816 |
716 String toString() => 'HTTP error $statusCode: $reason'; | 817 String toString() => 'HTTP error $statusCode: $reason'; |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
761 return new Directory(entry); | 862 return new Directory(entry); |
762 } | 863 } |
763 | 864 |
764 /** | 865 /** |
765 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 866 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
766 */ | 867 */ |
767 Uri _getUri(uri) { | 868 Uri _getUri(uri) { |
768 if (uri is Uri) return uri; | 869 if (uri is Uri) return uri; |
769 return new Uri.fromString(uri); | 870 return new Uri.fromString(uri); |
770 } | 871 } |
OLD | NEW |