OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 import 'dart:async'; | 6 import 'dart:async'; |
7 import 'dart:collection'; | 7 import 'dart:collection'; |
8 import 'dart:convert'; | 8 import 'dart:convert'; |
9 import 'dart:io'; | 9 import 'dart:io'; |
10 | 10 |
| 11 import 'package:async/async.dart'; |
11 import 'package:path/path.dart' as path; | 12 import 'package:path/path.dart' as path; |
12 import 'package:pool/pool.dart'; | 13 import 'package:pool/pool.dart'; |
13 import 'package:http/http.dart' show ByteStream; | 14 import 'package:http/http.dart' show ByteStream; |
14 import 'package:http_multi_server/http_multi_server.dart'; | 15 import 'package:http_multi_server/http_multi_server.dart'; |
15 import 'package:stack_trace/stack_trace.dart'; | 16 import 'package:stack_trace/stack_trace.dart'; |
16 | 17 |
17 import 'exit_codes.dart' as exit_codes; | 18 import 'exit_codes.dart' as exit_codes; |
18 import 'exceptions.dart'; | 19 import 'exceptions.dart'; |
19 import 'error_group.dart'; | 20 import 'error_group.dart'; |
20 import 'log.dart' as log; | 21 import 'log.dart' as log; |
(...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
187 } | 188 } |
188 | 189 |
189 /// Writes [stream] to a new file at path [file]. | 190 /// Writes [stream] to a new file at path [file]. |
190 /// | 191 /// |
191 /// Replaces any file already at that path. Completes when the file is done | 192 /// Replaces any file already at that path. Completes when the file is done |
192 /// being written. | 193 /// being written. |
193 Future<String> createFileFromStream(Stream<List<int>> stream, String file) { | 194 Future<String> createFileFromStream(Stream<List<int>> stream, String file) { |
194 // TODO(nweiz): remove extra logging when we figure out the windows bot issue. | 195 // TODO(nweiz): remove extra logging when we figure out the windows bot issue. |
195 log.io("Creating $file from stream."); | 196 log.io("Creating $file from stream."); |
196 | 197 |
197 return _descriptorPool.withResource(() { | 198 return _descriptorPool.withResource/*<Future<String>>*/(() async { |
198 return stream.pipe(new File(file).openWrite()).then((_) { | 199 await stream.pipe(new File(file).openWrite()); |
199 log.fine("Created $file from stream."); | 200 log.fine("Created $file from stream."); |
200 return file; | 201 return file; |
201 }); | |
202 }); | 202 }); |
203 } | 203 } |
204 | 204 |
205 /// Copies all files in [files] to the directory [destination]. | 205 /// Copies all files in [files] to the directory [destination]. |
206 /// | 206 /// |
207 /// Their locations in [destination] will be determined by their relative | 207 /// Their locations in [destination] will be determined by their relative |
208 /// location to [baseDir]. Any existing files at those paths will be | 208 /// location to [baseDir]. Any existing files at those paths will be |
209 /// overwritten. | 209 /// overwritten. |
210 void copyFiles(Iterable<String> files, String baseDir, String destination) { | 210 void copyFiles(Iterable<String> files, String baseDir, String destination) { |
211 for (var file in files) { | 211 for (var file in files) { |
(...skipping 420 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
632 Future flushThenExit(int status) { | 632 Future flushThenExit(int status) { |
633 return Future.wait([ | 633 return Future.wait([ |
634 stdout.close(), | 634 stdout.close(), |
635 stderr.close() | 635 stderr.close() |
636 ]).then((_) => exit(status)); | 636 ]).then((_) => exit(status)); |
637 } | 637 } |
638 | 638 |
639 /// Returns a [EventSink] that pipes all data to [consumer] and a [Future] that | 639 /// Returns a [EventSink] that pipes all data to [consumer] and a [Future] that |
640 /// will succeed when [EventSink] is closed or fail with any errors that occur | 640 /// will succeed when [EventSink] is closed or fail with any errors that occur |
641 /// while writing. | 641 /// while writing. |
642 Pair<EventSink, Future> consumerToSink(StreamConsumer consumer) { | 642 Pair<EventSink/*<T>*/, Future> consumerToSink/*<T>*/( |
643 var controller = new StreamController(sync: true); | 643 StreamConsumer/*<T>*/ consumer) { |
| 644 var controller = new StreamController/*<T>*/(sync: true); |
644 var done = controller.stream.pipe(consumer); | 645 var done = controller.stream.pipe(consumer); |
645 return new Pair<EventSink, Future>(controller.sink, done); | 646 return new Pair(controller.sink, done); |
646 } | 647 } |
647 | 648 |
648 // TODO(nweiz): remove this when issue 7786 is fixed. | 649 // TODO(nweiz): remove this when issue 7786 is fixed. |
649 /// Pipes all data and errors from [stream] into [sink]. | 650 /// Pipes all data and errors from [stream] into [sink]. |
650 /// | 651 /// |
651 /// When [stream] is done, the returned [Future] is completed and [sink] is | 652 /// When [stream] is done, the returned [Future] is completed and [sink] is |
652 /// closed if [closeSink] is true. | 653 /// closed if [closeSink] is true. |
653 /// | 654 /// |
654 /// When an error occurs on [stream], that error is passed to [sink]. If | 655 /// When an error occurs on [stream], that error is passed to [sink]. If |
655 /// [cancelOnError] is true, [Future] will be completed successfully and no | 656 /// [cancelOnError] is true, [Future] will be completed successfully and no |
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
794 | 795 |
795 /// Creates a new [PubProcess] wrapping [process]. | 796 /// Creates a new [PubProcess] wrapping [process]. |
796 PubProcess(Process process) | 797 PubProcess(Process process) |
797 : _process = process { | 798 : _process = process { |
798 var errorGroup = new ErrorGroup(); | 799 var errorGroup = new ErrorGroup(); |
799 | 800 |
800 var pair = consumerToSink(process.stdin); | 801 var pair = consumerToSink(process.stdin); |
801 _stdin = pair.first; | 802 _stdin = pair.first; |
802 _stdinClosed = errorGroup.registerFuture(pair.last); | 803 _stdinClosed = errorGroup.registerFuture(pair.last); |
803 | 804 |
804 _stdout = new ByteStream( | 805 _stdout = new ByteStream(errorGroup.registerStream(process.stdout)); |
805 errorGroup.registerStream(process.stdout)); | 806 _stderr = new ByteStream(errorGroup.registerStream(process.stderr)); |
806 _stderr = new ByteStream( | |
807 errorGroup.registerStream(process.stderr)); | |
808 | 807 |
809 var exitCodeCompleter = new Completer(); | 808 var exitCodeCompleter = new Completer<int>(); |
810 _exitCode = errorGroup.registerFuture(exitCodeCompleter.future); | 809 _exitCode = errorGroup.registerFuture(exitCodeCompleter.future); |
811 _process.exitCode.then((code) => exitCodeCompleter.complete(code)); | 810 _process.exitCode.then((code) => exitCodeCompleter.complete(code)); |
812 } | 811 } |
813 | 812 |
814 /// Sends [signal] to the underlying process. | 813 /// Sends [signal] to the underlying process. |
815 bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) => | 814 bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) => |
816 _process.kill(signal); | 815 _process.kill(signal); |
817 } | 816 } |
818 | 817 |
819 /// Calls [fn] with appropriately modified arguments. | 818 /// Calls [fn] with appropriately modified arguments. |
820 /// | 819 /// |
821 /// [fn] should have the same signature as [Process.start], except that the | 820 /// [fn] should have the same signature as [Process.start], except that the |
822 /// returned value may have any return type. | 821 /// returned value may have any return type. |
823 _doProcess(Function fn, String executable, List<String> args, | 822 _doProcess(Function fn, String executable, List<String> args, |
824 {String workingDir, Map<String, String> environment, | 823 {String workingDir, Map<String, String> environment, |
825 bool runInShell: false}) { | 824 bool runInShell: false}) { |
826 // TODO(rnystrom): Should dart:io just handle this? | 825 // TODO(rnystrom): Should dart:io just handle this? |
827 // Spawning a process on Windows will not look for the executable in the | 826 // Spawning a process on Windows will not look for the executable in the |
828 // system path. So, if executable looks like it needs that (i.e. it doesn't | 827 // system path. So, if executable looks like it needs that (i.e. it doesn't |
829 // have any path separators in it), then spawn it through a shell. | 828 // have any path separators in it), then spawn it through a shell. |
830 if ((Platform.operatingSystem == "windows") && | 829 if ((Platform.operatingSystem == "windows") && |
831 (executable.indexOf('\\') == -1)) { | 830 (executable.indexOf('\\') == -1)) { |
832 args = flatten(["/c", executable, args]); | 831 args = ["/c", executable]..addAll(args); |
833 executable = "cmd"; | 832 executable = "cmd"; |
834 } | 833 } |
835 | 834 |
836 log.process(executable, args, workingDir == null ? '.' : workingDir); | 835 log.process(executable, args, workingDir == null ? '.' : workingDir); |
837 | 836 |
838 return fn(executable, args, | 837 return fn(executable, args, |
839 workingDirectory: workingDir, | 838 workingDirectory: workingDir, |
840 environment: environment, | 839 environment: environment, |
841 runInShell: runInShell); | 840 runInShell: runInShell); |
842 } | 841 } |
843 | 842 |
844 /// Updates [path]'s modification time. | 843 /// Updates [path]'s modification time. |
845 void touch(String path) { | 844 void touch(String path) { |
846 var file = new File(path).openSync(mode: FileMode.APPEND); | 845 var file = new File(path).openSync(mode: FileMode.APPEND); |
847 var originalLength = file.lengthSync(); | 846 var originalLength = file.lengthSync(); |
848 file.writeByteSync(0); | 847 file.writeByteSync(0); |
849 file.truncateSync(originalLength); | 848 file.truncateSync(originalLength); |
850 file.closeSync(); | 849 file.closeSync(); |
851 } | 850 } |
852 | 851 |
853 /// Creates a temporary directory and passes its path to [fn]. | 852 /// Creates a temporary directory and passes its path to [fn]. |
854 /// | 853 /// |
855 /// Once the [Future] returned by [fn] completes, the temporary directory and | 854 /// Once the [Future] returned by [fn] completes, the temporary directory and |
856 /// all its contents are deleted. [fn] can also return `null`, in which case | 855 /// all its contents are deleted. [fn] can also return `null`, in which case |
857 /// the temporary directory is deleted immediately afterwards. | 856 /// the temporary directory is deleted immediately afterwards. |
858 /// | 857 /// |
859 /// Returns a future that completes to the value that the future returned from | 858 /// Returns a future that completes to the value that the future returned from |
860 /// [fn] completes to. | 859 /// [fn] completes to. |
861 Future withTempDir(Future fn(String path)) { | 860 Future/*<T>*/ withTempDir/*<T>*/(Future/*<T>*/ fn(String path)) async { |
862 return new Future.sync(() { | 861 var tempDir = createSystemTempDir(); |
863 var tempDir = createSystemTempDir(); | 862 try { |
864 return new Future.sync(() => fn(tempDir)) | 863 return await fn(tempDir); |
865 .whenComplete(() => deleteEntry(tempDir)); | 864 } finally { |
866 }); | 865 deleteEntry(tempDir); |
| 866 } |
867 } | 867 } |
868 | 868 |
869 /// Binds an [HttpServer] to [host] and [port]. | 869 /// Binds an [HttpServer] to [host] and [port]. |
870 /// | 870 /// |
871 /// If [host] is "localhost", this will automatically listen on both the IPv4 | 871 /// If [host] is "localhost", this will automatically listen on both the IPv4 |
872 /// and IPv6 loopback addresses. | 872 /// and IPv6 loopback addresses. |
873 Future<HttpServer> bindServer(String host, int port) async { | 873 Future<HttpServer> bindServer(String host, int port) async { |
874 var server = host == 'localhost' | 874 var server = host == 'localhost' |
875 ? await HttpMultiServer.loopback(port) | 875 ? await HttpMultiServer.loopback(port) |
876 : await HttpServer.bind(host, port); | 876 : await HttpServer.bind(host, port); |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
988 }); | 988 }); |
989 } | 989 } |
990 | 990 |
991 /// Create a .tar.gz archive from a list of entries. | 991 /// Create a .tar.gz archive from a list of entries. |
992 /// | 992 /// |
993 /// Each entry can be a [String], [Directory], or [File] object. The root of | 993 /// Each entry can be a [String], [Directory], or [File] object. The root of |
994 /// the archive is considered to be [baseDir], which defaults to the current | 994 /// the archive is considered to be [baseDir], which defaults to the current |
995 /// working directory. | 995 /// working directory. |
996 /// | 996 /// |
997 /// Returns a [ByteStream] that emits the contents of the archive. | 997 /// Returns a [ByteStream] that emits the contents of the archive. |
998 ByteStream createTarGz(List contents, {baseDir}) { | 998 ByteStream createTarGz(List contents, {String baseDir}) { |
999 return new ByteStream(futureStream(new Future.sync(() async { | 999 return new ByteStream(StreamCompleter.fromFuture(new Future.sync(() async { |
1000 var buffer = new StringBuffer(); | 1000 var buffer = new StringBuffer(); |
1001 buffer.write('Creating .tag.gz stream containing:\n'); | 1001 buffer.write('Creating .tag.gz stream containing:\n'); |
1002 contents.forEach((file) => buffer.write('$file\n')); | 1002 contents.forEach((file) => buffer.write('$file\n')); |
1003 log.fine(buffer.toString()); | 1003 log.fine(buffer.toString()); |
1004 | 1004 |
1005 if (baseDir == null) baseDir = path.current; | 1005 if (baseDir == null) baseDir = path.current; |
1006 baseDir = path.absolute(baseDir); | 1006 baseDir = path.absolute(baseDir); |
1007 contents = contents.map((entry) { | 1007 contents = contents.map((entry) { |
1008 entry = path.absolute(entry); | 1008 entry = path.absolute(entry); |
1009 if (!path.isWithin(baseDir, entry)) { | 1009 if (!path.isWithin(baseDir, entry)) { |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1076 | 1076 |
1077 // TODO(rnystrom): Remove this and change to returning one string. | 1077 // TODO(rnystrom): Remove this and change to returning one string. |
1078 static List<String> _toLines(String output) { | 1078 static List<String> _toLines(String output) { |
1079 var lines = splitLines(output); | 1079 var lines = splitLines(output); |
1080 if (!lines.isEmpty && lines.last == "") lines.removeLast(); | 1080 if (!lines.isEmpty && lines.last == "") lines.removeLast(); |
1081 return lines; | 1081 return lines; |
1082 } | 1082 } |
1083 | 1083 |
1084 bool get success => exitCode == exit_codes.SUCCESS; | 1084 bool get success => exitCode == exit_codes.SUCCESS; |
1085 } | 1085 } |
OLD | NEW |