| 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'); |
| 11 #import('dart:isolate'); | 11 #import('dart:isolate'); |
| 12 #import('dart:uri'); | 12 #import('dart:uri'); |
| 13 |
| 13 #import('utils.dart'); | 14 #import('utils.dart'); |
| 14 | 15 |
| 16 bool _isGitInstalledCache; |
| 17 |
| 18 /// The cached Git command. |
| 19 String _gitCommandCache; |
| 20 |
| 15 /** Gets the current working directory. */ | 21 /** Gets the current working directory. */ |
| 16 String get workingDir => new File('.').fullPathSync(); | 22 String get workingDir => new File('.').fullPathSync(); |
| 17 | 23 |
| 18 /** | 24 /** |
| 19 * Prints the given string to `stderr` on its own line. | 25 * Prints the given string to `stderr` on its own line. |
| 20 */ | 26 */ |
| 21 void printError(value) { | 27 void printError(value) { |
| 22 stderr.writeString(value.toString()); | 28 stderr.writeString(value.toString()); |
| 23 stderr.writeString('\n'); | 29 stderr.writeString('\n'); |
| 24 } | 30 } |
| (...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 263 | 269 |
| 264 var command = 'ln'; | 270 var command = 'ln'; |
| 265 var args = ['-s', from, to]; | 271 var args = ['-s', from, to]; |
| 266 | 272 |
| 267 if (Platform.operatingSystem == 'windows') { | 273 if (Platform.operatingSystem == 'windows') { |
| 268 // Call mklink on Windows to create an NTFS junction point. Only works on | 274 // Call mklink on Windows to create an NTFS junction point. Only works on |
| 269 // Vista or later. (Junction points are available earlier, but the "mklink" | 275 // Vista or later. (Junction points are available earlier, but the "mklink" |
| 270 // command is not.) I'm using a junction point (/j) here instead of a soft | 276 // command is not.) I'm using a junction point (/j) here instead of a soft |
| 271 // link (/d) because the latter requires some privilege shenanigans that | 277 // link (/d) because the latter requires some privilege shenanigans that |
| 272 // I'm not sure how to specify from the command line. | 278 // I'm not sure how to specify from the command line. |
| 273 command = 'cmd'; | 279 command = 'mklink'; |
| 274 args = ['/c', 'mklink', '/j', to, from]; | 280 args = ['/j', to, from]; |
| 275 } | 281 } |
| 276 | 282 |
| 277 return runProcess(command, args).transform((result) { | 283 return runProcess(command, args).transform((result) { |
| 278 // TODO(rnystrom): Check exit code and output? | 284 // TODO(rnystrom): Check exit code and output? |
| 279 return new File(to); | 285 return new File(to); |
| 280 }); | 286 }); |
| 281 } | 287 } |
| 282 | 288 |
| 283 /** | 289 /** |
| 284 * Creates a new symlink that creates an alias from the package [from] to [to], | 290 * Creates a new symlink that creates an alias from the package [from] to [to], |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 358 | 364 |
| 359 var completer = new Completer<InputStream>(); | 365 var completer = new Completer<InputStream>(); |
| 360 var client = new HttpClient(); | 366 var client = new HttpClient(); |
| 361 var connection = client.getUrl(uri); | 367 var connection = client.getUrl(uri); |
| 362 | 368 |
| 363 connection.onError = (e) { | 369 connection.onError = (e) { |
| 364 // Show a friendly error if the URL couldn't be resolved. | 370 // Show a friendly error if the URL couldn't be resolved. |
| 365 if (e is SocketIOException && | 371 if (e is SocketIOException && |
| 366 (e.osError.errorCode == 8 || | 372 (e.osError.errorCode == 8 || |
| 367 e.osError.errorCode == -2 || | 373 e.osError.errorCode == -2 || |
| 368 e.osError.errorCode == -5)) { | 374 e.osError.errorCode == -5 || |
| 375 e.osError.errorCode == 11004)) { |
| 369 e = 'Could not resolve URL "${uri.origin}".'; | 376 e = 'Could not resolve URL "${uri.origin}".'; |
| 370 } | 377 } |
| 371 | 378 |
| 372 client.shutdown(); | 379 client.shutdown(); |
| 373 completer.completeException(e); | 380 completer.completeException(e); |
| 374 }; | 381 }; |
| 375 | 382 |
| 376 connection.onResponse = (response) { | 383 connection.onResponse = (response) { |
| 377 if (response.statusCode >= 400) { | 384 if (response.statusCode >= 400) { |
| 378 client.shutdown(); | 385 client.shutdown(); |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 432 * | 439 * |
| 433 * If [pipeStdout] and/or [pipeStderr] are set, all output from the subprocess's | 440 * If [pipeStdout] and/or [pipeStderr] are set, all output from the subprocess's |
| 434 * output streams are sent to the parent process's output streams. Output from | 441 * output streams are sent to the parent process's output streams. Output from |
| 435 * piped streams won't be available in the result object. | 442 * piped streams won't be available in the result object. |
| 436 */ | 443 */ |
| 437 Future<PubProcessResult> runProcess(String executable, List<String> args, | 444 Future<PubProcessResult> runProcess(String executable, List<String> args, |
| 438 [workingDir, Map<String, String> environment, bool pipeStdout = false, | 445 [workingDir, Map<String, String> environment, bool pipeStdout = false, |
| 439 bool pipeStderr = false]) { | 446 bool pipeStderr = false]) { |
| 440 int exitCode; | 447 int exitCode; |
| 441 | 448 |
| 449 // TODO(rnystrom): Should dart:io just handle this? |
| 450 // Spawning a process on Windows will not look for the executable in the |
| 451 // system path. So, if executable looks like it needs that (i.e. it doesn't |
| 452 // have any path separators in it), then spawn it through a shell. |
| 453 if ((Platform.operatingSystem == "windows") && |
| 454 (executable.indexOf('\\') == -1)) { |
| 455 args = flatten(["/c", executable, args]); |
| 456 executable = "cmd"; |
| 457 } |
| 458 |
| 442 final options = new ProcessOptions(); | 459 final options = new ProcessOptions(); |
| 443 if (workingDir != null) { | 460 if (workingDir != null) { |
| 444 options.workingDirectory = _getDirectory(workingDir).path; | 461 options.workingDirectory = _getDirectory(workingDir).path; |
| 445 } | 462 } |
| 446 options.environment = environment; | 463 options.environment = environment; |
| 447 | 464 |
| 448 final process = Process.start(executable, args, options); | 465 final process = Process.start(executable, args, options); |
| 449 | 466 |
| 450 final outStream = new StringInputStream(process.stdout); | 467 final outStream = new StringInputStream(process.stdout); |
| 451 final processStdout = <String>[]; | 468 final processStdout = <String>[]; |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 513 return true; | 530 return true; |
| 514 }); | 531 }); |
| 515 input.then((value) { | 532 input.then((value) { |
| 516 if (completer.future.isComplete) return; | 533 if (completer.future.isComplete) return; |
| 517 timer.cancel(); | 534 timer.cancel(); |
| 518 completer.complete(value); | 535 completer.complete(value); |
| 519 }); | 536 }); |
| 520 return completer.future; | 537 return completer.future; |
| 521 } | 538 } |
| 522 | 539 |
| 523 /// The cached Git command. | 540 /// Tests whether or not the git command-line app is available for use. |
| 524 String _gitCommandCache; | 541 Future<bool> get isGitInstalled { |
| 542 if (_isGitInstalledCache != null) { |
| 543 // TODO(rnystrom): The sleep is to pump the message queue. Can use |
| 544 // Future.immediate() when #3356 is fixed. |
| 545 return sleep(0).transform((_) => _isGitInstalledCache); |
| 525 | 546 |
| 526 /// Tests whether or not the git command-line app is available for use. | 547 return _gitCommand.transform((git) => git != null); |
| 527 Future<bool> get isGitInstalled => _gitCommand.transform((git) => git != null); | 548 } |
| 549 } |
| 528 | 550 |
| 529 /// Run a git process with [args] from [workingDir]. | 551 /// Run a git process with [args] from [workingDir]. |
| 530 Future<PubProcessResult> runGit(List<String> args, [String workingDir]) => | 552 Future<PubProcessResult> runGit(List<String> args, [String workingDir]) => |
| 531 _gitCommand.chain((git) => runProcess(git, args, workingDir)); | 553 _gitCommand.chain((git) => runProcess(git, args, workingDir)); |
| 532 | 554 |
| 533 /// Returns the name of the git command-line app, or null if Git could not be | 555 /// Returns the name of the git command-line app, or null if Git could not be |
| 534 /// found on the user's PATH. | 556 /// found on the user's PATH. |
| 535 Future<String> get _gitCommand { | 557 Future<String> get _gitCommand { |
| 536 // TODO(nweiz): Just use Future.immediate once issue 3356 is fixed. | 558 // TODO(nweiz): Just use Future.immediate once issue 3356 is fixed. |
| 537 if (_gitCommandCache != null) { | 559 if (_gitCommandCache != null) { |
| 538 return sleep(0).transform((_) => _gitCommandCache); | 560 return sleep(0).transform((_) => _gitCommandCache); |
| 539 } | 561 } |
| 540 | 562 |
| 541 return _tryGitCommand("git").chain((success) { | 563 return _tryGitCommand("git").chain((success) { |
| (...skipping 30 matching lines...) Expand all Loading... |
| 572 }); | 594 }); |
| 573 | 595 |
| 574 return completer.future; | 596 return completer.future; |
| 575 } | 597 } |
| 576 | 598 |
| 577 /** | 599 /** |
| 578 * Extracts a `.tar.gz` file from [stream] to [destination], which can be a | 600 * Extracts a `.tar.gz` file from [stream] to [destination], which can be a |
| 579 * directory or a path. Returns whether or not the extraction was successful. | 601 * directory or a path. Returns whether or not the extraction was successful. |
| 580 */ | 602 */ |
| 581 Future<bool> extractTarGz(InputStream stream, destination) { | 603 Future<bool> extractTarGz(InputStream stream, destination) { |
| 604 destination = _getPath(destination); |
| 605 |
| 606 if (Platform.operatingSystem == "windows") { |
| 607 return _extractTarGzWindows(stream, destination); |
| 608 } |
| 609 |
| 582 var process = Process.start("tar", | 610 var process = Process.start("tar", |
| 583 ["--extract", "--gunzip", "--directory", _getPath(destination)]); | 611 ["--extract", "--gunzip", "--directory", destination]); |
| 584 var completer = new Completer<int>(); | 612 var completer = new Completer<int>(); |
| 585 | 613 |
| 586 // Wait for the process to be fully started before writing to its | 614 // Wait for the process to be fully started before writing to its |
| 587 // stdin stream. | 615 // stdin stream. |
| 588 process.onStart = () { | 616 process.onStart = () { |
| 589 stream.pipe(process.stdin); | 617 stream.pipe(process.stdin); |
| 590 process.stdout.pipe(stdout, close: false); | 618 process.stdout.pipe(stdout, close: false); |
| 591 process.stderr.pipe(stderr, close: false); | 619 process.stderr.pipe(stderr, close: false); |
| 592 | 620 |
| 593 process.onExit = completer.complete; | 621 process.onExit = completer.complete; |
| 594 process.onError = completer.completeException; | 622 process.onError = completer.completeException; |
| 595 }; | 623 }; |
| 596 | 624 |
| 597 return completer.future.transform((exitCode) => exitCode == 0); | 625 return completer.future.transform((exitCode) => exitCode == 0); |
| 598 } | 626 } |
| 599 | 627 |
| 628 Future<bool> _extractTarGzWindows(InputStream stream, String destination) { |
| 629 // Find 7zip. |
| 630 var scriptDir = new Path(new Options().script).directoryPath; |
| 631 |
| 632 // Note: This line of code gets munged by create_sdk.py to be the correct |
| 633 // relative path to 7zip in the SDK. |
| 634 var pathTo7zip = '../../third_party/7zip/7za.exe'; |
| 635 |
| 636 var command = scriptDir.append(pathTo7zip).canonicalize().toNativePath(); |
| 637 |
| 638 // 7zip can't unarchive from gzip -> tar -> destination all in one step so |
| 639 // we spawn it twice and pipe them together. |
| 640 var completer = new Completer<int>(); |
| 641 var gzipProcess = Process.start(command, ["e", "-si", "-tgzip", '-so']); |
| 642 var tarProcess = Process.start(command, |
| 643 ["e", "-si", "-ttar", '-o"$destination"']); |
| 644 |
| 645 stream.pipe(gzipProcess.stdin); |
| 646 gzipProcess.stdout.pipe(tarProcess.stdin); |
| 647 |
| 648 tarProcess.onExit = completer.complete; |
| 649 gzipProcess.onError = completer.completeException; |
| 650 gzipProcess.onError = completer.completeException; |
| 651 |
| 652 return completer.future.transform((exitCode) => exitCode == 0); |
| 653 } |
| 654 |
| 600 /** | 655 /** |
| 601 * Exception thrown when an HTTP operation fails. | 656 * Exception thrown when an HTTP operation fails. |
| 602 */ | 657 */ |
| 603 class HttpException implements Exception { | 658 class HttpException implements Exception { |
| 604 final int statusCode; | 659 final int statusCode; |
| 605 final String reason; | 660 final String reason; |
| 606 | 661 |
| 607 const HttpException(this.statusCode, this.reason); | 662 const HttpException(this.statusCode, this.reason); |
| 608 } | 663 } |
| 609 | 664 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 650 return new Directory(entry); | 705 return new Directory(entry); |
| 651 } | 706 } |
| 652 | 707 |
| 653 /** | 708 /** |
| 654 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 709 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
| 655 */ | 710 */ |
| 656 Uri _getUri(uri) { | 711 Uri _getUri(uri) { |
| 657 if (uri is Uri) return uri; | 712 if (uri is Uri) return uri; |
| 658 return new Uri.fromString(uri); | 713 return new Uri.fromString(uri); |
| 659 } | 714 } |
| OLD | NEW |