| Index: utils/pub/io.dart
 | 
| diff --git a/utils/pub/io.dart b/utils/pub/io.dart
 | 
| index f4d846334a7003be3ea7051de7aa76972480af69..5f5f70ce4046f7ae14bd554e26743e8359378fe9 100644
 | 
| --- a/utils/pub/io.dart
 | 
| +++ b/utils/pub/io.dart
 | 
| @@ -13,9 +13,8 @@ import 'dart:uri';
 | 
|  
 | 
|  // TODO(nweiz): Make this import better.
 | 
|  import '../../pkg/http/lib/http.dart' as http;
 | 
| -import 'curl_client.dart';
 | 
| -import 'log.dart' as log;
 | 
|  import 'utils.dart';
 | 
| +import 'curl_client.dart';
 | 
|  
 | 
|  bool _isGitInstalledCache;
 | 
|  
 | 
| @@ -28,6 +27,15 @@ String get currentWorkingDir => new File('.').fullPathSync();
 | 
|  final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?");
 | 
|  
 | 
|  /**
 | 
| + * Prints the given string to `stderr` on its own line.
 | 
| + */
 | 
| +void printError(value) {
 | 
| +  stderr.writeString(value.toString());
 | 
| +  stderr.writeString('\n');
 | 
| +}
 | 
| +
 | 
| +
 | 
| +/**
 | 
|   * Joins a number of path string parts into a single path. Handles
 | 
|   * platform-specific path separators. Parts can be [String], [Directory], or
 | 
|   * [File] objects.
 | 
| @@ -114,10 +122,7 @@ Future<bool> exists(path) {
 | 
|   * the result.
 | 
|   */
 | 
|  Future<bool> fileExists(file) {
 | 
| -  var path = _getPath(file);
 | 
| -  return log.ioAsync("Seeing if file $path exists.",
 | 
| -      new File(path).exists(),
 | 
| -      (exists) => "File $path ${exists ? 'exists' : 'does not exist'}.");
 | 
| +  return new File(_getPath(file)).exists();
 | 
|  }
 | 
|  
 | 
|  /**
 | 
| @@ -125,17 +130,7 @@ Future<bool> fileExists(file) {
 | 
|   * a [File].
 | 
|   */
 | 
|  Future<String> readTextFile(file) {
 | 
| -  var path = _getPath(file);
 | 
| -  return log.ioAsync("Reading text file $path.",
 | 
| -      new File(path).readAsString(Encoding.UTF_8),
 | 
| -      (contents) {
 | 
| -        // Sanity check: don't spew a huge file.
 | 
| -        if (contents.length < 1024 * 1024) {
 | 
| -          return "Read $path. Contents:\n$contents";
 | 
| -        } else {
 | 
| -          return "Read ${contents.length} characters from $path.";
 | 
| -        }
 | 
| -      });
 | 
| +  return new File(_getPath(file)).readAsString(Encoding.UTF_8);
 | 
|  }
 | 
|  
 | 
|  /**
 | 
| @@ -143,21 +138,10 @@ Future<String> readTextFile(file) {
 | 
|   * [contents] to it. Completes when the file is written and closed.
 | 
|   */
 | 
|  Future<File> writeTextFile(file, String contents) {
 | 
| -  var path = _getPath(file);
 | 
| -  file = new File(path);
 | 
| -
 | 
| -  // Sanity check: don't spew a huge file.
 | 
| -  log.io("Writing ${contents.length} characters to text file $path.");
 | 
| -  if (contents.length < 1024 * 1024) {
 | 
| -    log.fine("Contents:\n$contents");
 | 
| -  }
 | 
| -
 | 
| +  file = new File(_getPath(file));
 | 
|    return file.open(FileMode.WRITE).chain((opened) {
 | 
|      return opened.writeString(contents).chain((ignore) {
 | 
| -        return opened.close().transform((_) {
 | 
| -          log.fine("Wrote text file $path.");
 | 
| -          return file;
 | 
| -        });
 | 
| +        return opened.close().transform((ignore) => file);
 | 
|      });
 | 
|    });
 | 
|  }
 | 
| @@ -167,9 +151,7 @@ Future<File> writeTextFile(file, String contents) {
 | 
|   * [Future] that completes when the deletion is done.
 | 
|   */
 | 
|  Future<File> deleteFile(file) {
 | 
| -  var path = _getPath(file);
 | 
| -  return log.ioAsync("delete file $path",
 | 
| -      new File(path).delete());
 | 
| +  return new File(_getPath(file)).delete();
 | 
|  }
 | 
|  
 | 
|  /// Writes [stream] to a new file at [path], which may be a [String] or a
 | 
| @@ -178,15 +160,12 @@ Future<File> deleteFile(file) {
 | 
|  Future<File> createFileFromStream(InputStream stream, path) {
 | 
|    path = _getPath(path);
 | 
|  
 | 
| -  log.io("Creating $path from stream.");
 | 
| -
 | 
|    var completer = new Completer<File>();
 | 
|    var file = new File(path);
 | 
|    var outputStream = file.openOutputStream();
 | 
|    stream.pipe(outputStream);
 | 
|  
 | 
|    outputStream.onClosed = () {
 | 
| -    log.fine("Created $path from stream.");
 | 
|      completer.complete(file);
 | 
|    };
 | 
|  
 | 
| @@ -199,11 +178,7 @@ Future<File> createFileFromStream(InputStream stream, path) {
 | 
|    }
 | 
|  
 | 
|    completeError(error) {
 | 
| -    if (!completer.isComplete) {
 | 
| -      completer.completeException(error, stackTrace);
 | 
| -    } else {
 | 
| -      log.fine("Got error after stream was closed: $error");
 | 
| -    }
 | 
| +    if (!completer.isComplete) completer.completeException(error, stackTrace);
 | 
|    }
 | 
|  
 | 
|    stream.onError = completeError;
 | 
| @@ -218,8 +193,7 @@ Future<File> createFileFromStream(InputStream stream, path) {
 | 
|   */
 | 
|  Future<Directory> createDir(dir) {
 | 
|    dir = _getDirectory(dir);
 | 
| -  return log.ioAsync("create directory ${dir.path}",
 | 
| -      dir.create());
 | 
| +  return dir.create();
 | 
|  }
 | 
|  
 | 
|  /**
 | 
| @@ -229,15 +203,10 @@ Future<Directory> createDir(dir) {
 | 
|   */
 | 
|  Future<Directory> ensureDir(path) {
 | 
|    path = _getPath(path);
 | 
| -  log.fine("Ensuring directory $path exists.");
 | 
|    if (path == '.') return new Future.immediate(new Directory('.'));
 | 
|  
 | 
|    return dirExists(path).chain((exists) {
 | 
| -    if (exists) {
 | 
| -      log.fine("Directory $path already exists.");
 | 
| -      return new Future.immediate(new Directory(path));
 | 
| -    }
 | 
| -
 | 
| +    if (exists) return new Future.immediate(new Directory(path));
 | 
|      return ensureDir(dirname(path)).chain((_) {
 | 
|        var completer = new Completer<Directory>();
 | 
|        var future = createDir(path);
 | 
| @@ -245,10 +214,7 @@ Future<Directory> ensureDir(path) {
 | 
|          if (error is! DirectoryIOException) return false;
 | 
|          // Error 17 means the directory already exists (or 183 on Windows).
 | 
|          if (error.osError.errorCode != 17 &&
 | 
| -            error.osError.errorCode != 183) {
 | 
| -          log.fine("Got 'already exists' error when creating directory.");
 | 
| -          return false;
 | 
| -        }
 | 
| +            error.osError.errorCode != 183) return false;
 | 
|  
 | 
|          completer.complete(_getDirectory(path));
 | 
|          return true;
 | 
| @@ -267,8 +233,7 @@ Future<Directory> ensureDir(path) {
 | 
|   */
 | 
|  Future<Directory> createTempDir([dir = '']) {
 | 
|    dir = _getDirectory(dir);
 | 
| -  return log.ioAsync("create temp directory ${dir.path}",
 | 
| -      dir.createTemp());
 | 
| +  return dir.createTemp();
 | 
|  }
 | 
|  
 | 
|  /**
 | 
| @@ -277,8 +242,7 @@ Future<Directory> createTempDir([dir = '']) {
 | 
|   */
 | 
|  Future<Directory> deleteDir(dir) {
 | 
|    dir = _getDirectory(dir);
 | 
| -  return log.ioAsync("delete directory ${dir.path}",
 | 
| -      dir.delete(recursive: true));
 | 
| +  return dir.delete(recursive: true);
 | 
|  }
 | 
|  
 | 
|  /**
 | 
| @@ -293,17 +257,12 @@ Future<List<String>> listDir(dir,
 | 
|    final contents = <String>[];
 | 
|  
 | 
|    dir = _getDirectory(dir);
 | 
| -  log.io("Listing directory ${dir.path}.");
 | 
|    var lister = dir.list(recursive: recursive);
 | 
|  
 | 
|    lister.onDone = (done) {
 | 
|      // TODO(rnystrom): May need to sort here if it turns out onDir and onFile
 | 
|      // aren't guaranteed to be called in a certain order. So far, they seem to.
 | 
| -    if (done) {
 | 
| -      log.fine("Listed directory ${dir.path}:\n"
 | 
| -                "${Strings.join(contents, '\n')}");
 | 
| -      completer.complete(contents);
 | 
| -    }
 | 
| +    if (done) completer.complete(contents);
 | 
|    };
 | 
|  
 | 
|    // TODO(nweiz): remove this when issue 4061 is fixed.
 | 
| @@ -334,10 +293,7 @@ Future<List<String>> listDir(dir,
 | 
|   */
 | 
|  Future<bool> dirExists(dir) {
 | 
|    dir = _getDirectory(dir);
 | 
| -  return log.ioAsync("Seeing if directory ${dir.path} exists.",
 | 
| -      dir.exists(),
 | 
| -      (exists) => "Directory ${dir.path} "
 | 
| -                  "${exists ? 'exists' : 'does not exist'}.");
 | 
| +  return dir.exists();
 | 
|  }
 | 
|  
 | 
|  /**
 | 
| @@ -361,14 +317,8 @@ Future<Directory> cleanDir(dir) {
 | 
|  /// the destination directory.
 | 
|  Future<Directory> renameDir(from, String to) {
 | 
|    from = _getDirectory(from);
 | 
| -  log.io("Renaming directory ${from.path} to $to.");
 | 
|  
 | 
| -  if (Platform.operatingSystem != 'windows') {
 | 
| -    return from.rename(to).transform((dir) {
 | 
| -      log.fine("Renamed directory ${from.path} to $to.");
 | 
| -      return dir;
 | 
| -    });
 | 
| -  }
 | 
| +  if (Platform.operatingSystem != 'windows') return from.rename(to);
 | 
|  
 | 
|    // On Windows, we sometimes get failures where the directory is still in use
 | 
|    // when we try to move it. To be a bit more resilient, we wait and retry a
 | 
| @@ -376,17 +326,13 @@ Future<Directory> renameDir(from, String to) {
 | 
|    var attempts = 0;
 | 
|    attemptRename(_) {
 | 
|      attempts++;
 | 
| -    return from.rename(to).transform((dir) {
 | 
| -      log.fine("Renamed directory ${from.path} to $to.");
 | 
| -      return dir;
 | 
| -    }).transformException((e) {
 | 
| +    return from.rename(to).transformException((e) {
 | 
|        if (attempts >= 10) {
 | 
|          throw 'Could not move directory "${from.path}" to "$to". Gave up '
 | 
|                'after $attempts attempts.';
 | 
|        }
 | 
|  
 | 
|        // Wait a bit and try again.
 | 
| -      log.fine("Rename ${from.path} failed, retrying (attempt $attempts).");
 | 
|        return sleep(500).chain(attemptRename);
 | 
|      });
 | 
|  
 | 
| @@ -405,8 +351,6 @@ Future<File> createSymlink(from, to) {
 | 
|    from = _getPath(from);
 | 
|    to = _getPath(to);
 | 
|  
 | 
| -  log.fine("Create symlink $from -> $to.");
 | 
| -
 | 
|    var command = 'ln';
 | 
|    var args = ['-s', from, to];
 | 
|  
 | 
| @@ -438,15 +382,15 @@ Future<File> createPackageSymlink(String name, from, to,
 | 
|    // See if the package has a "lib" directory.
 | 
|    from = join(from, 'lib');
 | 
|    return dirExists(from).chain((exists) {
 | 
| -    log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'.");
 | 
|      if (exists) return createSymlink(from, to);
 | 
|  
 | 
|      // It's OK for the self link (i.e. the root package) to not have a lib
 | 
|      // directory since it may just be a leaf application that only has
 | 
|      // code in bin or web.
 | 
|      if (!isSelfLink) {
 | 
| -      log.warning('Warning: Package "$name" does not have a "lib" directory so '
 | 
| -                  'you will not be able to import any libraries from it.');
 | 
| +      printError(
 | 
| +          'Warning: Package "$name" does not have a "lib" directory so you '
 | 
| +          'will not be able to import any libraries from it.');
 | 
|      }
 | 
|  
 | 
|      return new Future.immediate(to);
 | 
| @@ -562,8 +506,6 @@ class PubHttpClient extends http.BaseClient {
 | 
|      : _inner = inner == null ? new http.Client() : inner;
 | 
|  
 | 
|    Future<http.StreamedResponse> send(http.BaseRequest request) {
 | 
| -    log.io("Sending HTTP request $request.");
 | 
| -
 | 
|      // TODO(nweiz): remove this when issue 4061 is fixed.
 | 
|      var stackTrace;
 | 
|      try {
 | 
| @@ -575,9 +517,6 @@ class PubHttpClient extends http.BaseClient {
 | 
|      // TODO(nweiz): Ideally the timeout would extend to reading from the
 | 
|      // response input stream, but until issue 3657 is fixed that's not feasible.
 | 
|      return timeout(_inner.send(request).chain((streamedResponse) {
 | 
| -      log.fine("Got response ${streamedResponse.statusCode} "
 | 
| -               "${streamedResponse.reasonPhrase}.");
 | 
| -
 | 
|        var status = streamedResponse.statusCode;
 | 
|        // 401 responses should be handled by the OAuth2 client. It's very
 | 
|        // unlikely that they'll be returned by non-OAuth2 requests.
 | 
| @@ -683,13 +622,9 @@ Future<PubProcessResult> runProcess(String executable, List<String> args,
 | 
|        if (!lines.isEmpty && lines.last == "") lines.removeLast();
 | 
|        return lines;
 | 
|      }
 | 
| -
 | 
| -    var pubResult = new PubProcessResult(toLines(result.stdout),
 | 
| +    return new PubProcessResult(toLines(result.stdout),
 | 
|                                  toLines(result.stderr),
 | 
|                                  result.exitCode);
 | 
| -
 | 
| -    log.processResult(executable, pubResult);
 | 
| -    return pubResult;
 | 
|    });
 | 
|  }
 | 
|  
 | 
| @@ -728,8 +663,6 @@ Future _doProcess(Function fn, String executable, List<String> args, workingDir,
 | 
|      environment.forEach((key, value) => options.environment[key] = value);
 | 
|    }
 | 
|  
 | 
| -  log.process(executable, args);
 | 
| -
 | 
|    return fn(executable, args, options);
 | 
|  }
 | 
|  
 | 
| @@ -798,10 +731,7 @@ Future withTempDir(Future fn(String path)) {
 | 
|      tempDir = dir;
 | 
|      return fn(tempDir.path);
 | 
|    });
 | 
| -  future.onComplete((_) {
 | 
| -    log.fine('Cleaning up temp directory ${tempDir.path}.');
 | 
| -    deleteDir(tempDir);
 | 
| -  });
 | 
| +  future.onComplete((_) => tempDir.delete(recursive: true));
 | 
|    return future;
 | 
|  }
 | 
|  
 | 
| @@ -874,8 +804,6 @@ Future<bool> _tryGitCommand(String command) {
 | 
|  Future<bool> extractTarGz(InputStream stream, destination) {
 | 
|    destination = _getPath(destination);
 | 
|  
 | 
| -  log.fine("Extracting .tar.gz stream to $destination.");
 | 
| -
 | 
|    if (Platform.operatingSystem == "windows") {
 | 
|      return _extractTarGzWindows(stream, destination);
 | 
|    }
 | 
| @@ -894,12 +822,7 @@ Future<bool> extractTarGz(InputStream stream, destination) {
 | 
|      return true;
 | 
|    });
 | 
|  
 | 
| -  return completer.future.transform((exitCode) {
 | 
| -    log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode.");
 | 
| -    // TODO(rnystrom): Does anything check this result value? If not, it should
 | 
| -    // throw on a bad exit code.
 | 
| -    return exitCode == 0;
 | 
| -  });
 | 
| +  return completer.future.transform((exitCode) => exitCode == 0);
 | 
|  }
 | 
|  
 | 
|  Future<bool> _extractTarGzWindows(InputStream stream, String destination) {
 | 
| @@ -956,7 +879,7 @@ Future<bool> _extractTarGzWindows(InputStream stream, String destination) {
 | 
|            '${Strings.join(result.stderr, "\n")}';
 | 
|      }
 | 
|  
 | 
| -    log.fine('Clean up 7zip temp directory ${tempDir.path}.');
 | 
| +    // Clean up the temp directory.
 | 
|      // TODO(rnystrom): Should also delete this if anything fails.
 | 
|      return deleteDir(tempDir);
 | 
|    }).transform((_) => true);
 | 
| @@ -967,9 +890,6 @@ Future<bool> _extractTarGzWindows(InputStream stream, String destination) {
 | 
|  /// considered to be [baseDir], which defaults to the current working directory.
 | 
|  /// Returns an [InputStream] that will emit the contents of the archive.
 | 
|  InputStream createTarGz(List contents, {baseDir}) {
 | 
| -  log.fine('Creating .tag.gz stream containing:');
 | 
| -  contents.forEach(log.fine);
 | 
| -
 | 
|    // TODO(nweiz): Propagate errors to the returned stream (including non-zero
 | 
|    // exit codes). See issue 3657.
 | 
|    var stream = new ListInputStream();
 | 
| @@ -1035,7 +955,7 @@ class PubHttpException implements Exception {
 | 
|    const PubHttpException(this.response);
 | 
|  
 | 
|    String toString() => 'HTTP error ${response.statusCode}: '
 | 
| -      '${response.reasonPhrase}';
 | 
| +    '${response.reasonPhrase}';
 | 
|  }
 | 
|  
 | 
|  /**
 | 
| 
 |