Chromium Code Reviews| 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 |