Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(20)

Side by Side Diff: utils/pub/io.dart

Issue 11783009: Big merge from experimental to bleeding edge. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « utils/pub/hosted_source.dart ('k') | utils/pub/lock_file.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 /// Helper functionality to make working with IO easier. 5 /// Helper functionality to make working with IO easier.
6 library io; 6 library io;
7 7
8 import 'dart:async';
8 import 'dart:io'; 9 import 'dart:io';
9 import 'dart:isolate'; 10 import 'dart:isolate';
10 import 'dart:json'; 11 import 'dart:json';
11 import 'dart:uri'; 12 import 'dart:uri';
12 13
13 import '../../pkg/path/lib/path.dart' as path; 14 import '../../pkg/path/lib/path.dart' as path;
14 import 'log.dart' as log; 15 import 'log.dart' as log;
15 import 'utils.dart'; 16 import 'utils.dart';
16 17
17 bool _isGitInstalledCache; 18 bool _isGitInstalledCache;
18 19
19 /// The cached Git command. 20 /// The cached Git command.
20 String _gitCommandCache; 21 String _gitCommandCache;
21 22
22 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); 23 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?");
23 24
24 /// Joins a number of path string parts into a single path. Handles 25 /// Joins a number of path string parts into a single path. Handles
25 /// platform-specific path separators. Parts can be [String], [Directory], or 26 /// platform-specific path separators. Parts can be [String], [Directory], or
26 /// [File] objects. 27 /// [File] objects.
27 String join(part1, [part2, part3, part4, part5, part6, part7, part8]) { 28 String join(part1, [part2, part3, part4, part5, part6, part7, part8]) {
28 var parts = [part1, part2, part3, part4, part5, part6, part7, part8] 29 var parts = [part1, part2, part3, part4, part5, part6, part7, part8]
29 .map((part) => part == null ? null : _getPath(part)); 30 .mappedBy((part) => part == null ? null : _getPath(part)).toList();
30 31
31 return path.join(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], 32 return path.join(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5],
32 parts[6], parts[7]); 33 parts[6], parts[7]);
33 } 34 }
34 35
35 /// Gets the basename, the file name without any leading directory path, for 36 /// Gets the basename, the file name without any leading directory path, for
36 /// [file], which can either be a [String], [File], or [Directory]. 37 /// [file], which can either be a [String], [File], or [Directory].
37 String basename(file) => path.basename(_getPath(file)); 38 String basename(file) => path.basename(_getPath(file));
38 39
39 /// Gets the the leading directory path for [file], which can either be a 40 /// Gets the the leading directory path for [file], which can either be a
(...skipping 11 matching lines...) Expand all
51 } 52 }
52 53
53 /// Returns the path to [target] from [base]. 54 /// Returns the path to [target] from [base].
54 String relativeTo(target, base) => path.relative(target, from: base); 55 String relativeTo(target, base) => path.relative(target, from: base);
55 56
56 /// Asynchronously determines if [path], which can be a [String] file path, a 57 /// Asynchronously determines if [path], which can be a [String] file path, a
57 /// [File], or a [Directory] exists on the file system. Returns a [Future] that 58 /// [File], or a [Directory] exists on the file system. Returns a [Future] that
58 /// completes with the result. 59 /// completes with the result.
59 Future<bool> exists(path) { 60 Future<bool> exists(path) {
60 path = _getPath(path); 61 path = _getPath(path);
61 return Futures.wait([fileExists(path), dirExists(path)]).transform((results) { 62 return Futures.wait([fileExists(path), dirExists(path)]).then((results) {
62 return results[0] || results[1]; 63 return results[0] || results[1];
63 }); 64 });
64 } 65 }
65 66
66 /// Asynchronously determines if [file], which can be a [String] file path or a 67 /// Asynchronously determines if [file], which can be a [String] file path or a
67 /// [File], exists on the file system. Returns a [Future] that completes with 68 /// [File], exists on the file system. Returns a [Future] that completes with
68 /// the result. 69 /// the result.
69 Future<bool> fileExists(file) { 70 Future<bool> fileExists(file) {
70 var path = _getPath(file); 71 var path = _getPath(file);
71 return log.ioAsync("Seeing if file $path exists.", 72 return log.ioAsync("Seeing if file $path exists.",
(...skipping 24 matching lines...) Expand all
96 Future<File> writeTextFile(file, String contents, {dontLogContents: false}) { 97 Future<File> writeTextFile(file, String contents, {dontLogContents: false}) {
97 var path = _getPath(file); 98 var path = _getPath(file);
98 file = new File(path); 99 file = new File(path);
99 100
100 // Sanity check: don't spew a huge file. 101 // Sanity check: don't spew a huge file.
101 log.io("Writing ${contents.length} characters to text file $path."); 102 log.io("Writing ${contents.length} characters to text file $path.");
102 if (!dontLogContents && contents.length < 1024 * 1024) { 103 if (!dontLogContents && contents.length < 1024 * 1024) {
103 log.fine("Contents:\n$contents"); 104 log.fine("Contents:\n$contents");
104 } 105 }
105 106
106 return file.open(FileMode.WRITE).chain((opened) { 107 return file.open(FileMode.WRITE).then((opened) {
107 return opened.writeString(contents).chain((ignore) { 108 return opened.writeString(contents).then((ignore) {
108 return opened.close().transform((_) { 109 return opened.close().then((_) {
109 log.fine("Wrote text file $path."); 110 log.fine("Wrote text file $path.");
110 return file; 111 return file;
111 }); 112 });
112 }); 113 });
113 }); 114 });
114 } 115 }
115 116
116 /// Asynchronously deletes [file], which can be a [String] or a [File]. Returns 117 /// Asynchronously deletes [file], which can be a [String] or a [File]. Returns
117 /// a [Future] that completes when the deletion is done. 118 /// a [Future] that completes when the deletion is done.
118 Future<File> deleteFile(file) { 119 Future<File> deleteFile(file) {
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
170 } 171 }
171 172
172 /// Ensures that [path] and all its parent directories exist. If they don't 173 /// Ensures that [path] and all its parent directories exist. If they don't
173 /// exist, creates them. Returns a [Future] that completes once all the 174 /// exist, creates them. Returns a [Future] that completes once all the
174 /// directories are created. 175 /// directories are created.
175 Future<Directory> ensureDir(path) { 176 Future<Directory> ensureDir(path) {
176 path = _getPath(path); 177 path = _getPath(path);
177 log.fine("Ensuring directory $path exists."); 178 log.fine("Ensuring directory $path exists.");
178 if (path == '.') return new Future.immediate(new Directory('.')); 179 if (path == '.') return new Future.immediate(new Directory('.'));
179 180
180 return dirExists(path).chain((exists) { 181 return dirExists(path).then((exists) {
181 if (exists) { 182 if (exists) {
182 log.fine("Directory $path already exists."); 183 log.fine("Directory $path already exists.");
183 return new Future.immediate(new Directory(path)); 184 return new Future.immediate(new Directory(path));
184 } 185 }
185 186
186 return ensureDir(dirname(path)).chain((_) { 187 return ensureDir(dirname(path)).then((_) {
187 var completer = new Completer<Directory>(); 188 return createDir(path)
188 var future = createDir(path); 189 .catchError((error) {
189 future.handleException((error) { 190 if (error is! DirectoryIOException) return false;
190 if (error is! DirectoryIOException) return false; 191 // Error 17 means the directory already exists (or 183 on Windows).
191 // Error 17 means the directory already exists (or 183 on Windows). 192 if (error.osError.errorCode != 17 &&
192 if (error.osError.errorCode != 17 && 193 error.osError.errorCode != 183) {
193 error.osError.errorCode != 183) {
194 log.fine("Got 'already exists' error when creating directory."); 194 log.fine("Got 'already exists' error when creating directory.");
195 return false; 195 return false;
196 } 196 }
197 197
198 completer.complete(_getDirectory(path)); 198 return _getDirectory(path);
199 return true; 199 });
200 });
201 future.then(completer.complete);
202 return completer.future;
203 }); 200 });
204 }); 201 });
205 } 202 }
206 203
207 /// Creates a temp directory whose name will be based on [dir] with a unique 204 /// Creates a temp directory whose name will be based on [dir] with a unique
208 /// suffix appended to it. If [dir] is not provided, a temp directory will be 205 /// suffix appended to it. If [dir] is not provided, a temp directory will be
209 /// created in a platform-dependent temporary location. Returns a [Future] that 206 /// created in a platform-dependent temporary location. Returns a [Future] that
210 /// completes when the directory is created. 207 /// completes when the directory is created.
211 Future<Directory> createTempDir([dir = '']) { 208 Future<Directory> createTempDir([dir = '']) {
212 dir = _getDirectory(dir); 209 dir = _getDirectory(dir);
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
303 return log.ioAsync("Seeing if directory ${dir.path} exists.", 300 return log.ioAsync("Seeing if directory ${dir.path} exists.",
304 dir.exists(), 301 dir.exists(),
305 (exists) => "Directory ${dir.path} " 302 (exists) => "Directory ${dir.path} "
306 "${exists ? 'exists' : 'does not exist'}."); 303 "${exists ? 'exists' : 'does not exist'}.");
307 } 304 }
308 305
309 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a 306 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a
310 /// new empty directory will be created. Returns a [Future] that completes when 307 /// new empty directory will be created. Returns a [Future] that completes when
311 /// the new clean directory is created. 308 /// the new clean directory is created.
312 Future<Directory> cleanDir(dir) { 309 Future<Directory> cleanDir(dir) {
313 return dirExists(dir).chain((exists) { 310 return dirExists(dir).then((exists) {
314 if (exists) { 311 if (exists) {
315 // Delete it first. 312 // Delete it first.
316 return deleteDir(dir).chain((_) => createDir(dir)); 313 return deleteDir(dir).then((_) => createDir(dir));
317 } else { 314 } else {
318 // Just create it. 315 // Just create it.
319 return createDir(dir); 316 return createDir(dir);
320 } 317 }
321 }); 318 });
322 } 319 }
323 320
324 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with 321 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with
325 /// the destination directory. 322 /// the destination directory.
326 Future<Directory> renameDir(from, String to) { 323 Future<Directory> renameDir(from, String to) {
327 from = _getDirectory(from); 324 from = _getDirectory(from);
328 log.io("Renaming directory ${from.path} to $to."); 325 log.io("Renaming directory ${from.path} to $to.");
329 326
330 return _attemptRetryable(() => from.rename(to)).transform((dir) { 327 return _attemptRetryable(() => from.rename(to)).then((dir) {
331 log.fine("Renamed directory ${from.path} to $to."); 328 log.fine("Renamed directory ${from.path} to $to.");
332 return dir; 329 return dir;
333 }); 330 });
334 } 331 }
335 332
336 /// On Windows, we sometimes get failures where the directory is still in use 333 /// On Windows, we sometimes get failures where the directory is still in use
337 /// when we try to do something with it. This is usually because the OS hasn't 334 /// when we try to do something with it. This is usually because the OS hasn't
338 /// noticed yet that a process using that directory has closed. To be a bit 335 /// noticed yet that a process using that directory has closed. To be a bit
339 /// more resilient, we wait and retry a few times. 336 /// more resilient, we wait and retry a few times.
340 /// 337 ///
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
378 if (Platform.operatingSystem == 'windows') { 375 if (Platform.operatingSystem == 'windows') {
379 // Call mklink on Windows to create an NTFS junction point. Only works on 376 // Call mklink on Windows to create an NTFS junction point. Only works on
380 // Vista or later. (Junction points are available earlier, but the "mklink" 377 // Vista or later. (Junction points are available earlier, but the "mklink"
381 // command is not.) I'm using a junction point (/j) here instead of a soft 378 // command is not.) I'm using a junction point (/j) here instead of a soft
382 // link (/d) because the latter requires some privilege shenanigans that 379 // link (/d) because the latter requires some privilege shenanigans that
383 // I'm not sure how to specify from the command line. 380 // I'm not sure how to specify from the command line.
384 command = 'mklink'; 381 command = 'mklink';
385 args = ['/j', to, from]; 382 args = ['/j', to, from];
386 } 383 }
387 384
388 return runProcess(command, args).transform((result) { 385 return runProcess(command, args).then((result) {
389 // TODO(rnystrom): Check exit code and output? 386 // TODO(rnystrom): Check exit code and output?
390 return new File(to); 387 return new File(to);
391 }); 388 });
392 } 389 }
393 390
394 /// Creates a new symlink that creates an alias from the `lib` directory of 391 /// Creates a new symlink that creates an alias from the `lib` directory of
395 /// package [from] to [to], both of which can be a [String], [File], or 392 /// package [from] to [to], both of which can be a [String], [File], or
396 /// [Directory]. Returns a [Future] which completes to the symlink file (i.e. 393 /// [Directory]. Returns a [Future] which completes to the symlink file (i.e.
397 /// [to]). If [from] does not have a `lib` directory, this shows a warning if 394 /// [to]). If [from] does not have a `lib` directory, this shows a warning if
398 /// appropriate and then does nothing. 395 /// appropriate and then does nothing.
399 Future<File> createPackageSymlink(String name, from, to, 396 Future<File> createPackageSymlink(String name, from, to,
400 {bool isSelfLink: false}) { 397 {bool isSelfLink: false}) {
401 // See if the package has a "lib" directory. 398 // See if the package has a "lib" directory.
402 from = join(from, 'lib'); 399 from = join(from, 'lib');
403 return dirExists(from).chain((exists) { 400 return dirExists(from).then((exists) {
404 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); 401 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'.");
405 if (exists) return createSymlink(from, to); 402 if (exists) return createSymlink(from, to);
406 403
407 // It's OK for the self link (i.e. the root package) to not have a lib 404 // It's OK for the self link (i.e. the root package) to not have a lib
408 // directory since it may just be a leaf application that only has 405 // directory since it may just be a leaf application that only has
409 // code in bin or web. 406 // code in bin or web.
410 if (!isSelfLink) { 407 if (!isSelfLink) {
411 log.warning('Warning: Package "$name" does not have a "lib" directory so ' 408 log.warning('Warning: Package "$name" does not have a "lib" directory so '
412 'you will not be able to import any libraries from it.'); 409 'you will not be able to import any libraries from it.');
413 } 410 }
(...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after
575 /// Spawns and runs the process located at [executable], passing in [args]. 572 /// Spawns and runs the process located at [executable], passing in [args].
576 /// Returns a [Future] that will complete with the results of the process after 573 /// Returns a [Future] that will complete with the results of the process after
577 /// it has ended. 574 /// it has ended.
578 /// 575 ///
579 /// The spawned process will inherit its parent's environment variables. If 576 /// The spawned process will inherit its parent's environment variables. If
580 /// [environment] is provided, that will be used to augment (not replace) the 577 /// [environment] is provided, that will be used to augment (not replace) the
581 /// the inherited variables. 578 /// the inherited variables.
582 Future<PubProcessResult> runProcess(String executable, List<String> args, 579 Future<PubProcessResult> runProcess(String executable, List<String> args,
583 {workingDir, Map<String, String> environment}) { 580 {workingDir, Map<String, String> environment}) {
584 return _doProcess(Process.run, executable, args, workingDir, environment) 581 return _doProcess(Process.run, executable, args, workingDir, environment)
585 .transform((result) { 582 .then((result) {
586 // TODO(rnystrom): Remove this and change to returning one string. 583 // TODO(rnystrom): Remove this and change to returning one string.
587 List<String> toLines(String output) { 584 List<String> toLines(String output) {
588 var lines = output.split(NEWLINE_PATTERN); 585 var lines = output.split(NEWLINE_PATTERN);
589 if (!lines.isEmpty && lines.last == "") lines.removeLast(); 586 if (!lines.isEmpty && lines.last == "") lines.removeLast();
590 return lines; 587 return lines;
591 } 588 }
592 589
593 var pubResult = new PubProcessResult(toLines(result.stdout), 590 var pubResult = new PubProcessResult(toLines(result.stdout),
594 toLines(result.stderr), 591 toLines(result.stderr),
595 result.exitCode); 592 result.exitCode);
596 593
597 log.processResult(executable, pubResult); 594 log.processResult(executable, pubResult);
598 return pubResult; 595 return pubResult;
599 }); 596 });
600 } 597 }
601 598
602 /// Spawns the process located at [executable], passing in [args]. Returns a 599 /// Spawns the process located at [executable], passing in [args]. Returns a
603 /// [Future] that will complete with the [Process] once it's been started. 600 /// [Future] that will complete with the [Process] once it's been started.
604 /// 601 ///
605 /// The spawned process will inherit its parent's environment variables. If 602 /// The spawned process will inherit its parent's environment variables. If
606 /// [environment] is provided, that will be used to augment (not replace) the 603 /// [environment] is provided, that will be used to augment (not replace) the
607 /// the inherited variables. 604 /// the inherited variables.
608 Future<Process> startProcess(String executable, List<String> args, 605 Future<Process> startProcess(String executable, List<String> args,
609 {workingDir, Map<String, String> environment}) => 606 {workingDir, Map<String, String> environment}) =>
610 _doProcess(Process.start, executable, args, workingDir, environment) 607 _doProcess(Process.start, executable, args, workingDir, environment)
611 .transform((process) => new _WrappedProcess(process)); 608 .then((process) => new _WrappedProcess(process));
612 609
613 /// A wrapper around [Process] that buffers the stdout and stderr to avoid 610 /// A wrapper around [Process] that buffers the stdout and stderr to avoid
614 /// running into issue 7218. 611 /// running into issue 7218.
615 class _WrappedProcess implements Process { 612 class _WrappedProcess implements Process {
616 final Process _process; 613 final Process _process;
617 final InputStream stderr; 614 final InputStream stderr;
618 final InputStream stdout; 615 final InputStream stdout;
619 616
620 OutputStream get stdin => _process.stdin; 617 OutputStream get stdin => _process.stdin;
621 618
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
664 661
665 /// Wraps [input] to provide a timeout. If [input] completes before 662 /// Wraps [input] to provide a timeout. If [input] completes before
666 /// [milliseconds] have passed, then the return value completes in the same way. 663 /// [milliseconds] have passed, then the return value completes in the same way.
667 /// However, if [milliseconds] pass before [input] has completed, it completes 664 /// However, if [milliseconds] pass before [input] has completed, it completes
668 /// with a [TimeoutException] with [description] (which should be a fragment 665 /// with a [TimeoutException] with [description] (which should be a fragment
669 /// describing the action that timed out). 666 /// describing the action that timed out).
670 /// 667 ///
671 /// Note that timing out will not cancel the asynchronous operation behind 668 /// Note that timing out will not cancel the asynchronous operation behind
672 /// [input]. 669 /// [input].
673 Future timeout(Future input, int milliseconds, String description) { 670 Future timeout(Future input, int milliseconds, String description) {
671 bool completed = false;
674 var completer = new Completer(); 672 var completer = new Completer();
675 var timer = new Timer(milliseconds, (_) { 673 var timer = new Timer(milliseconds, (_) {
676 if (completer.future.isComplete) return; 674 completer = true;
677 completer.completeException(new TimeoutException( 675 completer.completeError(new TimeoutException(
678 'Timed out while $description.')); 676 'Timed out while $description.'));
679 }); 677 });
680 input.handleException((e) { 678 input
681 if (completer.future.isComplete) return false; 679 .then((value) {
682 timer.cancel(); 680 if (completed) return;
683 completer.completeException(e, input.stackTrace); 681 timer.cancel();
684 return true; 682 completer.complete(value);
685 }); 683 })
686 input.then((value) { 684 .catchError((e) {
687 if (completer.future.isComplete) return; 685 if (completed) return;
688 timer.cancel(); 686 timer.cancel();
689 completer.complete(value); 687 completer.completeError(e.error, e.stackTrace);
690 }); 688 });
691 return completer.future; 689 return completer.future;
692 } 690 }
693 691
694 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] 692 /// Creates a temporary directory and passes its path to [fn]. Once the [Future]
695 /// returned by [fn] completes, the temporary directory and all its contents 693 /// returned by [fn] completes, the temporary directory and all its contents
696 /// will be deleted. 694 /// will be deleted.
697 Future withTempDir(Future fn(String path)) { 695 Future withTempDir(Future fn(String path)) {
698 var tempDir; 696 var tempDir;
699 var future = createTempDir().chain((dir) { 697 var future = createTempDir().then((dir) {
700 tempDir = dir; 698 tempDir = dir;
701 return fn(tempDir.path); 699 return fn(tempDir.path);
702 }); 700 });
703 future.onComplete((_) { 701 future.catchError((_) {}).then(_) {
704 log.fine('Cleaning up temp directory ${tempDir.path}.'); 702 log.fine('Cleaning up temp directory ${tempDir.path}.');
705 deleteDir(tempDir); 703 deleteDir(tempDir);
706 }); 704 });
707 return future; 705 return future;
708 } 706 }
709 707
710 /// Tests whether or not the git command-line app is available for use. 708 /// Tests whether or not the git command-line app is available for use.
711 Future<bool> get isGitInstalled { 709 Future<bool> get isGitInstalled {
712 if (_isGitInstalledCache != null) { 710 if (_isGitInstalledCache != null) {
713 // TODO(rnystrom): The sleep is to pump the message queue. Can use 711 // TODO(rnystrom): The sleep is to pump the message queue. Can use
714 // Future.immediate() when #3356 is fixed. 712 // Future.immediate() when #3356 is fixed.
715 return sleep(0).transform((_) => _isGitInstalledCache); 713 return sleep(0).then((_) => _isGitInstalledCache);
716 } 714 }
717 715
718 return _gitCommand.transform((git) => git != null); 716 return _gitCommand.then((git) => git != null);
719 } 717 }
720 718
721 /// Run a git process with [args] from [workingDir]. 719 /// Run a git process with [args] from [workingDir].
722 Future<PubProcessResult> runGit(List<String> args, 720 Future<PubProcessResult> runGit(List<String> args,
723 {String workingDir, Map<String, String> environment}) { 721 {String workingDir, Map<String, String> environment}) {
724 return _gitCommand.chain((git) => runProcess(git, args, 722 return _gitCommand.then((git) => runProcess(git, args,
725 workingDir: workingDir, environment: environment)); 723 workingDir: workingDir, environment: environment));
726 } 724 }
727 725
728 /// Returns the name of the git command-line app, or null if Git could not be 726 /// Returns the name of the git command-line app, or null if Git could not be
729 /// found on the user's PATH. 727 /// found on the user's PATH.
730 Future<String> get _gitCommand { 728 Future<String> get _gitCommand {
731 // TODO(nweiz): Just use Future.immediate once issue 3356 is fixed. 729 // TODO(nweiz): Just use Future.immediate once issue 3356 is fixed.
732 if (_gitCommandCache != null) { 730 if (_gitCommandCache != null) {
733 return sleep(0).transform((_) => _gitCommandCache); 731 return sleep(0).then((_) => _gitCommandCache);
734 } 732 }
735 733
736 return _tryGitCommand("git").chain((success) { 734 return _tryGitCommand("git").then((success) {
737 if (success) return new Future.immediate("git"); 735 if (success) return new Future.immediate("git");
738 736
739 // Git is sometimes installed on Windows as `git.cmd` 737 // Git is sometimes installed on Windows as `git.cmd`
740 return _tryGitCommand("git.cmd").transform((success) { 738 return _tryGitCommand("git.cmd").then((success) {
741 if (success) return "git.cmd"; 739 if (success) return "git.cmd";
742 return null; 740 return null;
743 }); 741 });
744 }).transform((command) { 742 }).then((command) {
745 _gitCommandCache = command; 743 _gitCommandCache = command;
746 return command; 744 return command;
747 }); 745 });
748 } 746 }
749 747
750 /// Checks whether [command] is the Git command for this computer. 748 /// Checks whether [command] is the Git command for this computer.
751 Future<bool> _tryGitCommand(String command) { 749 Future<bool> _tryGitCommand(String command) {
752 var completer = new Completer<bool>(); 750 var completer = new Completer<bool>();
753 751
754 // If "git --version" prints something familiar, git is working. 752 // If "git --version" prints something familiar, git is working.
755 var future = runProcess(command, ["--version"]); 753 var future = runProcess(command, ["--version"]);
756 754
757 future.then((results) { 755 future.then((results) {
758 var regex = new RegExp("^git version"); 756 var regex = new RegExp("^git version");
759 completer.complete(results.stdout.length == 1 && 757 completer.complete(results.stdout.length == 1 &&
760 regex.hasMatch(results.stdout[0])); 758 regex.hasMatch(results.stdout[0]));
761 }); 759 }).catchError((err) {
762
763 future.handleException((err) {
764 // If the process failed, they probably don't have it. 760 // If the process failed, they probably don't have it.
765 completer.complete(false); 761 completer.complete(false);
766 return true;
767 }); 762 });
768 763
769 return completer.future; 764 return completer.future;
770 } 765 }
771 766
772 /// Extracts a `.tar.gz` file from [stream] to [destination], which can be a 767 /// Extracts a `.tar.gz` file from [stream] to [destination], which can be a
773 /// directory or a path. Returns whether or not the extraction was successful. 768 /// directory or a path. Returns whether or not the extraction was successful.
774 Future<bool> extractTarGz(InputStream stream, destination) { 769 Future<bool> extractTarGz(InputStream stream, destination) {
775 destination = _getPath(destination); 770 destination = _getPath(destination);
776 771
777 log.fine("Extracting .tar.gz stream to $destination."); 772 log.fine("Extracting .tar.gz stream to $destination.");
778 773
779 if (Platform.operatingSystem == "windows") { 774 if (Platform.operatingSystem == "windows") {
780 return _extractTarGzWindows(stream, destination); 775 return _extractTarGzWindows(stream, destination);
781 } 776 }
782 777
783 var completer = new Completer<int>(); 778 var completer = new Completer<int>();
784 var processFuture = startProcess("tar", 779 var processFuture = startProcess("tar",
785 ["--extract", "--gunzip", "--directory", destination]); 780 ["--extract", "--gunzip", "--directory", destination]);
786 processFuture.then((process) { 781 processFuture.then((process) {
787 process.onExit = completer.complete; 782 process.onExit = completer.complete;
788 stream.pipe(process.stdin); 783 stream.pipe(process.stdin);
789 process.stdout.pipe(stdout, close: false); 784 process.stdout.pipe(stdout, close: false);
790 process.stderr.pipe(stderr, close: false); 785 process.stderr.pipe(stderr, close: false);
791 }); 786 }).catchError((e) {
792 processFuture.handleException((error) { 787 completer.completeError(e.error, e.stackTrace);
793 completer.completeException(error, processFuture.stackTrace);
794 return true;
795 }); 788 });
796 789
797 return completer.future.transform((exitCode) { 790 return completer.future.then((exitCode) {
798 log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode."); 791 log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode.");
799 // TODO(rnystrom): Does anything check this result value? If not, it should 792 // TODO(rnystrom): Does anything check this result value? If not, it should
800 // throw on a bad exit code. 793 // throw on a bad exit code.
801 return exitCode == 0; 794 return exitCode == 0;
802 }); 795 });
803 } 796 }
804 797
805 Future<bool> _extractTarGzWindows(InputStream stream, String destination) { 798 Future<bool> _extractTarGzWindows(InputStream stream, String destination) {
806 // TODO(rnystrom): In the repo's history, there is an older implementation of 799 // TODO(rnystrom): In the repo's history, there is an older implementation of
807 // this that does everything in memory by piping streams directly together 800 // this that does everything in memory by piping streams directly together
808 // instead of writing out temp files. The code is simpler, but unfortunately, 801 // instead of writing out temp files. The code is simpler, but unfortunately,
809 // 7zip seems to periodically fail when we invoke it from Dart and tell it to 802 // 7zip seems to periodically fail when we invoke it from Dart and tell it to
810 // read from stdin instead of a file. Consider resurrecting that version if 803 // read from stdin instead of a file. Consider resurrecting that version if
811 // we can figure out why it fails. 804 // we can figure out why it fails.
812 805
813 // Note: This line of code gets munged by create_sdk.py to be the correct 806 // Note: This line of code gets munged by create_sdk.py to be the correct
814 // relative path to 7zip in the SDK. 807 // relative path to 7zip in the SDK.
815 var pathTo7zip = '../../third_party/7zip/7za.exe'; 808 var pathTo7zip = '../../third_party/7zip/7za.exe';
816 var command = relativeToPub(pathTo7zip); 809 var command = relativeToPub(pathTo7zip);
817 810
818 var tempDir; 811 var tempDir;
819 812
820 // TODO(rnystrom): Use withTempDir(). 813 // TODO(rnystrom): Use withTempDir().
821 return createTempDir().chain((temp) { 814 return createTempDir().then((temp) {
822 // Write the archive to a temp file. 815 // Write the archive to a temp file.
823 tempDir = temp; 816 tempDir = temp;
824 return createFileFromStream(stream, join(tempDir, 'data.tar.gz')); 817 return createFileFromStream(stream, join(tempDir, 'data.tar.gz'));
825 }).chain((_) { 818 }).then((_) {
826 // 7zip can't unarchive from gzip -> tar -> destination all in one step 819 // 7zip can't unarchive from gzip -> tar -> destination all in one step
827 // first we un-gzip it to a tar file. 820 // first we un-gzip it to a tar file.
828 // Note: Setting the working directory instead of passing in a full file 821 // Note: Setting the working directory instead of passing in a full file
829 // path because 7zip says "A full path is not allowed here." 822 // path because 7zip says "A full path is not allowed here."
830 return runProcess(command, ['e', 'data.tar.gz'], workingDir: tempDir); 823 return runProcess(command, ['e', 'data.tar.gz'], workingDir: tempDir);
831 }).chain((result) { 824 }).then((result) {
832 if (result.exitCode != 0) { 825 if (result.exitCode != 0) {
833 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n' 826 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n'
834 '${Strings.join(result.stdout, "\n")}\n' 827 '${Strings.join(result.stdout, "\n")}\n'
835 '${Strings.join(result.stderr, "\n")}'; 828 '${Strings.join(result.stderr, "\n")}';
836 } 829 }
837 // Find the tar file we just created since we don't know its name. 830 // Find the tar file we just created since we don't know its name.
838 return listDir(tempDir); 831 return listDir(tempDir);
839 }).chain((files) { 832 }).then((files) {
840 var tarFile; 833 var tarFile;
841 for (var file in files) { 834 for (var file in files) {
842 if (path.extension(file) == '.tar') { 835 if (path.extension(file) == '.tar') {
843 tarFile = file; 836 tarFile = file;
844 break; 837 break;
845 } 838 }
846 } 839 }
847 840
848 if (tarFile == null) throw 'The gzip file did not contain a tar file.'; 841 if (tarFile == null) throw 'The gzip file did not contain a tar file.';
849 842
850 // Untar the archive into the destination directory. 843 // Untar the archive into the destination directory.
851 return runProcess(command, ['x', tarFile], workingDir: destination); 844 return runProcess(command, ['x', tarFile], workingDir: destination);
852 }).chain((result) { 845 }).then((result) {
853 if (result.exitCode != 0) { 846 if (result.exitCode != 0) {
854 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n' 847 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n'
855 '${Strings.join(result.stdout, "\n")}\n' 848 '${Strings.join(result.stdout, "\n")}\n'
856 '${Strings.join(result.stderr, "\n")}'; 849 '${Strings.join(result.stderr, "\n")}';
857 } 850 }
858 851
859 log.fine('Clean up 7zip temp directory ${tempDir.path}.'); 852 log.fine('Clean up 7zip temp directory ${tempDir.path}.');
860 // TODO(rnystrom): Should also delete this if anything fails. 853 // TODO(rnystrom): Should also delete this if anything fails.
861 return deleteDir(tempDir); 854 return deleteDir(tempDir);
862 }).transform((_) => true); 855 }).then((_) => true);
863 } 856 }
864 857
865 /// Create a .tar.gz archive from a list of entries. Each entry can be a 858 /// Create a .tar.gz archive from a list of entries. Each entry can be a
866 /// [String], [Directory], or [File] object. The root of the archive is 859 /// [String], [Directory], or [File] object. The root of the archive is
867 /// considered to be [baseDir], which defaults to the current working directory. 860 /// considered to be [baseDir], which defaults to the current working directory.
868 /// Returns an [InputStream] that will emit the contents of the archive. 861 /// Returns an [InputStream] that will emit the contents of the archive.
869 InputStream createTarGz(List contents, {baseDir}) { 862 InputStream createTarGz(List contents, {baseDir}) {
870 var buffer = new StringBuffer(); 863 var buffer = new StringBuffer();
871 buffer.add('Creating .tag.gz stream containing:\n'); 864 buffer.add('Creating .tag.gz stream containing:\n');
872 contents.forEach((file) => buffer.add('$file\n')); 865 contents.forEach((file) => buffer.add('$file\n'));
873 log.fine(buffer.toString()); 866 log.fine(buffer.toString());
874 867
875 // TODO(nweiz): Propagate errors to the returned stream (including non-zero 868 // TODO(nweiz): Propagate errors to the returned stream (including non-zero
876 // exit codes). See issue 3657. 869 // exit codes). See issue 3657.
877 var stream = new ListInputStream(); 870 var stream = new ListInputStream();
878 871
879 if (baseDir == null) baseDir = path.current; 872 if (baseDir == null) baseDir = path.current;
880 baseDir = getFullPath(baseDir); 873 baseDir = getFullPath(baseDir);
881 contents = contents.map((entry) { 874 contents = contents.mappedBy((entry) {
882 entry = getFullPath(entry); 875 entry = getFullPath(entry);
883 if (!isBeneath(entry, baseDir)) { 876 if (!isBeneath(entry, baseDir)) {
884 throw 'Entry $entry is not inside $baseDir.'; 877 throw 'Entry $entry is not inside $baseDir.';
885 } 878 }
886 return relativeTo(entry, baseDir); 879 return relativeTo(entry, baseDir);
887 }); 880 }).toList();
888 881
889 if (Platform.operatingSystem != "windows") { 882 if (Platform.operatingSystem != "windows") {
890 var args = ["--create", "--gzip", "--directory", baseDir]; 883 var args = ["--create", "--gzip", "--directory", baseDir];
891 args.addAll(contents.map(_getPath)); 884 args.addAll(contents.mappedBy(_getPath));
892 // TODO(nweiz): It's possible that enough command-line arguments will make 885 // TODO(nweiz): It's possible that enough command-line arguments will make
893 // the process choke, so at some point we should save the arguments to a 886 // the process choke, so at some point we should save the arguments to a
894 // file and pass them in via --files-from for tar and -i@filename for 7zip. 887 // file and pass them in via --files-from for tar and -i@filename for 7zip.
895 startProcess("tar", args).then((process) { 888 startProcess("tar", args).then((process) {
896 pipeInputToInput(process.stdout, stream); 889 pipeInputToInput(process.stdout, stream);
897 890
898 // Drain and discard 7zip's stderr. 7zip writes its normal output to 891 // Drain and discard 7zip's stderr. 7zip writes its normal output to
899 // stderr. We don't want to show that since it's meaningless. 892 // stderr. We don't want to show that since it's meaningless.
900 // TODO(rnystrom): Should log this and display it if an actual error 893 // TODO(rnystrom): Should log this and display it if an actual error
901 // occurs. 894 // occurs.
902 consumeInputStream(process.stderr); 895 consumeInputStream(process.stderr);
903 }); 896 });
904 return stream; 897 return stream;
905 } 898 }
906 899
907 withTempDir((tempDir) { 900 withTempDir((tempDir) {
908 // Create the tar file. 901 // Create the tar file.
909 var tarFile = join(tempDir, "intermediate.tar"); 902 var tarFile = join(tempDir, "intermediate.tar");
910 var args = ["a", "-w$baseDir", tarFile]; 903 var args = ["a", "-w$baseDir", tarFile];
911 args.addAll(contents.map((entry) => '-i!"$entry"')); 904 args.addAll(contents.mappedBy((entry) => '-i!"$entry"'));
912 905
913 // Note: This line of code gets munged by create_sdk.py to be the correct 906 // Note: This line of code gets munged by create_sdk.py to be the correct
914 // relative path to 7zip in the SDK. 907 // relative path to 7zip in the SDK.
915 var pathTo7zip = '../../third_party/7zip/7za.exe'; 908 var pathTo7zip = '../../third_party/7zip/7za.exe';
916 var command = relativeToPub(pathTo7zip); 909 var command = relativeToPub(pathTo7zip);
917 910
918 // We're passing 'baseDir' both as '-w' and setting it as the working 911 // We're passing 'baseDir' both as '-w' and setting it as the working
919 // directory explicitly here intentionally. The former ensures that the 912 // directory explicitly here intentionally. The former ensures that the
920 // files added to the archive have the correct relative path in the archive. 913 // files added to the archive have the correct relative path in the archive.
921 // The latter enables relative paths in the "-i" args to be resolved. 914 // The latter enables relative paths in the "-i" args to be resolved.
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
971 Directory _getDirectory(entry) { 964 Directory _getDirectory(entry) {
972 if (entry is Directory) return entry; 965 if (entry is Directory) return entry;
973 return new Directory(entry); 966 return new Directory(entry);
974 } 967 }
975 968
976 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. 969 /// Gets a [Uri] for [uri], which can either already be one, or be a [String].
977 Uri _getUri(uri) { 970 Uri _getUri(uri) {
978 if (uri is Uri) return uri; 971 if (uri is Uri) return uri;
979 return new Uri.fromString(uri); 972 return new Uri.fromString(uri);
980 } 973 }
OLDNEW
« no previous file with comments | « utils/pub/hosted_source.dart ('k') | utils/pub/lock_file.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698