Chromium Code Reviews| Index: utils/pub/io.dart |
| diff --git a/utils/pub/io.dart b/utils/pub/io.dart |
| index ed5dee68be2a1c265dfd56364385e676b3c56f50..e7db79c9505e672d4180f8e8863de0ff67e659c7 100644 |
| --- a/utils/pub/io.dart |
| +++ b/utils/pub/io.dart |
| @@ -228,12 +228,11 @@ Future<Directory> deleteDir(dir) { |
| /** |
| * Asynchronously lists the contents of [dir], which can be a [String] directory |
| * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents |
| - * (defaults to `false`). If [includeSpecialFiles] is `true`, includes |
| - * hidden `.DS_Store` files (defaults to `false`, other hidden files may be |
| - * omitted later). |
| + * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files |
| + * beginning with `.` (defaults to `false`). |
| */ |
| Future<List<String>> listDir(dir, |
| - [bool recursive = false, bool includeSpecialFiles = false]) { |
| + {bool recursive: false, bool includeHiddenFiles: false}) { |
| final completer = new Completer<List<String>>(); |
| final contents = <String>[]; |
| @@ -249,9 +248,7 @@ Future<List<String>> listDir(dir, |
| lister.onError = (error) => completer.completeException(error); |
| lister.onDir = (file) => contents.add(file); |
| lister.onFile = (file) { |
| - if (!includeSpecialFiles) { |
| - if (basename(file) == '.DS_Store') return; |
| - } |
| + if (!includeHiddenFiles && basename(file).startsWith('.')) return; |
| contents.add(file); |
| }; |
| @@ -372,6 +369,41 @@ String relativeToPub(String path) { |
| return scriptDir.append(path).canonicalize().toNativePath(); |
| } |
| +/// A StringInputStream reading from stdin. |
| +StringInputStream _stringStdin = new StringInputStream(stdin); |
|
Bob Nystrom
2012/11/26 23:39:52
"StringInputStream" -> "final".
nweiz
2012/11/27 20:15:54
Done.
|
| + |
| +/// Returns a single line read from a [StringInputStream]. By default, reads |
| +/// from stdin. |
| +/// |
| +/// A [StringInputStream] passed to this should have no callbacks registered. |
| +Future<String> readLine([StringInputStream stream]) { |
| + if (stream == null) stream = _stringStdin; |
| + if (stream.closed) return new Future.immediate(''); |
| + void removeCallbacks() { |
| + stream.onClosed = null; |
| + stream.onLine = null; |
| + stream.onError = null; |
| + } |
| + |
| + var completer = new Completer(); |
| + stream.onClosed = () { |
| + removeCallbacks(); |
| + completer.complete(''); |
| + }; |
| + |
| + stream.onLine = () { |
| + removeCallbacks(); |
| + completer.complete(stream.readLine()); |
| + }; |
| + |
| + stream.onError = (e) { |
| + removeCallbacks(); |
| + completer.completeException(e); |
| + }; |
| + |
| + return completer.future; |
| +} |
| + |
| // TODO(nweiz): make this configurable |
| /** |
| * The amount of time in milliseconds to allow HTTP requests before assuming |
| @@ -432,7 +464,7 @@ Future<InputStream> httpGet(uri) { |
| Future<String> httpGetString(uri) { |
| var future = httpGet(uri).chain((stream) => consumeInputStream(stream)) |
| .transform((bytes) => new String.fromCharCodes(bytes)); |
| - return timeout(future, HTTP_TIMEOUT, 'Timed out while fetching URL "$uri".'); |
| + return timeout(future, HTTP_TIMEOUT, 'fetching URL "$uri"'); |
| } |
| /** |
| @@ -455,6 +487,8 @@ void pipeInputToInput(InputStream source, ListInputStream sink, |
| * Buffers all input from an InputStream and returns it as a future. |
| */ |
| Future<List<int>> consumeInputStream(InputStream stream) { |
| + if (stream.closed) return new Future.immediate(<int>[]); |
| + |
| var completer = new Completer<List<int>>(); |
| var buffer = <int>[]; |
| stream.onClosed = () => completer.complete(buffer); |
| @@ -463,22 +497,56 @@ Future<List<int>> consumeInputStream(InputStream stream) { |
| return completer.future; |
| } |
| +/// Buffers all input from a StringInputStream and returns it as a future. |
| +Future<String> consumeStringInputStream(StringInputStream stream) { |
| + if (stream.closed) return new Future.immediate(''); |
| + |
| + var completer = new Completer<String>(); |
| + var buffer = new StringBuffer(); |
| + stream.onClosed = () => completer.complete(buffer.toString()); |
| + stream.onData = () => buffer.add(stream.read()); |
| + stream.onError = (e) => completer.completeException(e); |
| + return completer.future; |
| +} |
| + |
| /// Spawns and runs the process located at [executable], passing in [args]. |
| -/// Returns a [Future] that will complete the results of the process after it |
| -/// has ended. |
| +/// Returns a [Future] that will complete with the results of the process after |
| +/// it has ended. |
| /// |
| /// The spawned process will inherit its parent's environment variables. If |
| /// [environment] is provided, that will be used to augment (not replace) the |
| /// the inherited variables. |
| -/// |
| -/// If [pipeStdout] and/or [pipeStderr] are set, all output from the |
| -/// subprocess's output streams are sent to the parent process's output streams. |
| -/// Output from piped streams won't be available in the result object. |
| Future<PubProcessResult> runProcess(String executable, List<String> args, |
| - {workingDir, Map<String, String> environment, bool pipeStdout: false, |
| - bool pipeStderr: false}) { |
| - int exitCode; |
| + {workingDir, Map<String, String> environment}) { |
| + return _doProcess(Process.run, executable, args, workingDir, environment) |
| + .transform((result) { |
| + // TODO(rnystrom): Remove this and change to returning one string. |
| + List<String> toLines(String output) { |
| + var lines = output.split(NEWLINE_PATTERN); |
| + if (!lines.isEmpty && lines.last == "") lines.removeLast(); |
| + return lines; |
| + } |
| + return new PubProcessResult(toLines(result.stdout), |
| + toLines(result.stderr), |
| + result.exitCode); |
| + }); |
| +} |
| +/// Spawns the process located at [executable], passing in [args]. Returns a |
| +/// [Future] that will complete with the [Process] once it's been started. |
| +/// |
| +/// The spawned process will inherit its parent's environment variables. If |
| +/// [environment] is provided, that will be used to augment (not replace) the |
| +/// the inherited variables. |
| +Future<Process> startProcess(String executable, List<String> args, |
| + {workingDir, Map<String, String> environment}) => |
| + _doProcess(Process.start, executable, args, workingDir, environment); |
| + |
| +/// Calls [fn] with appropriately modified arguments. [fn] should have the same |
| +/// signature as [Process.start], except that the returned [Future] may have a |
| +/// type other than [Process]. |
| +Future _doProcess(Function fn, String executable, List<String> args, workingDir, |
| + Map<String, String> environment) { |
| // TODO(rnystrom): Should dart:io just handle this? |
| // Spawning a process on Windows will not look for the executable in the |
| // system path. So, if executable looks like it needs that (i.e. it doesn't |
| @@ -499,34 +567,25 @@ Future<PubProcessResult> runProcess(String executable, List<String> args, |
| environment.forEach((key, value) => options.environment[key] = value); |
| } |
| - var future = Process.run(executable, args, options); |
| - return future.transform((result) { |
| - // TODO(rnystrom): Remove this and change to returning one string. |
| - List<String> toLines(String output) { |
| - var lines = output.split(NEWLINE_PATTERN); |
| - if (!lines.isEmpty && lines.last == "") lines.removeLast(); |
| - return lines; |
| - } |
| - return new PubProcessResult(toLines(result.stdout), |
| - toLines(result.stderr), |
| - result.exitCode); |
| - }); |
| + return fn(executable, args, options); |
| } |
| /** |
| * Wraps [input] to provide a timeout. If [input] completes before |
| * [milliseconds] have passed, then the return value completes in the same way. |
| * However, if [milliseconds] pass before [input] has completed, it completes |
| - * with a [TimeoutException] with [message]. |
| + * with a [TimeoutException] with [description] (which should be a fragment |
| + * describing the action that timed out). |
| * |
| * Note that timing out will not cancel the asynchronous operation behind |
| * [input]. |
| */ |
| -Future timeout(Future input, int milliseconds, String message) { |
| +Future timeout(Future input, int milliseconds, String description) { |
| var completer = new Completer(); |
| var timer = new Timer(milliseconds, (_) { |
| if (completer.future.isComplete) return; |
| - completer.completeException(new TimeoutException(message)); |
| + completer.completeException(new TimeoutException( |
| + 'Timed out while $description.')); |
| }); |
| input.handleException((e) { |
| if (completer.future.isComplete) return false; |
| @@ -704,6 +763,48 @@ Future<bool> _extractTarGzWindows(InputStream stream, String destination) { |
| }).transform((_) => true); |
| } |
| +/// Create a .tar.gz archive from a list of entries. Each entry can be a |
| +/// [String], [Directory], or [File] object. Returns an [InputStream] that will |
| +/// emit the contents of the archive. |
| +InputStream createTarGz(List contents) { |
| + // TODO(nweiz): Propagate errors to the returned stream (including non-zero |
| + // exit codes). See issue 3657. |
| + var stream = new ListInputStream(); |
| + |
| + if (Platform.operatingSystem != "windows") { |
| + var args = ["--create", "--gzip"]; |
| + 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
|
| + startProcess("tar", args).then((process) { |
| + pipeInputToInput(process.stdout, stream); |
| + process.stderr.pipe(stderr, close: false); |
| + }); |
| + return stream; |
| + } |
| + |
| + 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
|
| + // Create the tar file. |
| + var tarFile = join(tempDir, "intermediate.tar"); |
| + var args = ["a", tarFile]; |
| + args.addAll(contents.map((entry) => '-i!"$entry"')); |
| + |
| + // Note: This line of code gets munged by create_sdk.py to be the correct |
| + // relative path to 7zip in the SDK. |
| + var pathTo7zip = '../../third_party/7zip/7za.exe'; |
| + var command = relativeToPub(pathTo7zip); |
| + |
| + return runProcess(command, args).chain((_) { |
| + // GZIP it. 7zip doesn't support doing both as a single operation. Send |
| + // the output to stdout. |
| + args = ["a", "not used", "-so", tarFile]; |
| + return startProcess(command, args); |
| + }).transform((process) { |
| + pipeInputToInput(process.stdout, stream); |
| + process.stderr.pipe(stderr, close: false); |
| + }); |
| + }); |
| + return stream; |
| +} |
| + |
| /** |
| * Exception thrown when an HTTP operation fails. |
| */ |