Chromium Code Reviews| 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 |
| 14 // TODO(nweiz): Make this import better. | 14 // TODO(nweiz): Make this import better. |
| 15 import '../../pkg/http/lib/http.dart' as http; | 15 import '../../pkg/http/lib/http.dart' as http; |
| 16 import 'curl_client.dart'; | |
| 17 import 'log.dart' as log; | |
| 16 import 'utils.dart'; | 18 import 'utils.dart'; |
| 17 import 'curl_client.dart'; | |
| 18 | 19 |
| 19 bool _isGitInstalledCache; | 20 bool _isGitInstalledCache; |
| 20 | 21 |
| 21 /// The cached Git command. | 22 /// The cached Git command. |
| 22 String _gitCommandCache; | 23 String _gitCommandCache; |
| 23 | 24 |
| 24 /** Gets the current working directory. */ | 25 /** Gets the current working directory. */ |
| 25 String get currentWorkingDir => new File('.').fullPathSync(); | 26 String get currentWorkingDir => new File('.').fullPathSync(); |
| 26 | 27 |
| 27 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); | 28 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); |
| 28 | 29 |
| 29 /** | 30 /** |
| 30 * Prints the given string to `stderr` on its own line. | |
| 31 */ | |
| 32 void printError(value) { | |
| 33 stderr.writeString(value.toString()); | |
| 34 stderr.writeString('\n'); | |
| 35 } | |
| 36 | |
| 37 | |
| 38 /** | |
| 39 * Joins a number of path string parts into a single path. Handles | 31 * Joins a number of path string parts into a single path. Handles |
| 40 * platform-specific path separators. Parts can be [String], [Directory], or | 32 * platform-specific path separators. Parts can be [String], [Directory], or |
| 41 * [File] objects. | 33 * [File] objects. |
| 42 */ | 34 */ |
| 43 String join(part1, [part2, part3, part4]) { | 35 String join(part1, [part2, part3, part4]) { |
| 44 final parts = sanitizePath(part1).split('/'); | 36 final parts = sanitizePath(part1).split('/'); |
| 45 | 37 |
| 46 for (final part in [part2, part3, part4]) { | 38 for (final part in [part2, part3, part4]) { |
| 47 if (part == null) continue; | 39 if (part == null) continue; |
| 48 | 40 |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 115 return results[0] || results[1]; | 107 return results[0] || results[1]; |
| 116 }); | 108 }); |
| 117 } | 109 } |
| 118 | 110 |
| 119 /** | 111 /** |
| 120 * Asynchronously determines if [file], which can be a [String] file path or a | 112 * Asynchronously determines if [file], which can be a [String] file path or a |
| 121 * [File], exists on the file system. Returns a [Future] that completes with | 113 * [File], exists on the file system. Returns a [Future] that completes with |
| 122 * the result. | 114 * the result. |
| 123 */ | 115 */ |
| 124 Future<bool> fileExists(file) { | 116 Future<bool> fileExists(file) { |
| 125 return new File(_getPath(file)).exists(); | 117 var path = _getPath(file); |
| 118 return log.ioAsync("Seeing if file $path exists.", | |
| 119 new File(path).exists(), | |
| 120 (exists) => "File $path ${exists ? 'exists' : 'does not exist'}."); | |
| 126 } | 121 } |
| 127 | 122 |
| 128 /** | 123 /** |
| 129 * Reads the contents of the text file [file], which can either be a [String] or | 124 * Reads the contents of the text file [file], which can either be a [String] or |
| 130 * a [File]. | 125 * a [File]. |
| 131 */ | 126 */ |
| 132 Future<String> readTextFile(file) { | 127 Future<String> readTextFile(file) { |
| 133 return new File(_getPath(file)).readAsString(Encoding.UTF_8); | 128 var path = _getPath(file); |
| 129 return log.ioAsync("Reading text file $path.", | |
| 130 new File(path).readAsString(Encoding.UTF_8), | |
| 131 (contents) { | |
| 132 // Sanity check: don't spew a huge file. | |
| 133 if (contents.length < 1024 * 1024) { | |
| 134 return "Read $path. Contents:\n$contents"; | |
| 135 } else { | |
| 136 return "Read ${contents.length} characters from $path."; | |
| 137 } | |
| 138 }); | |
| 134 } | 139 } |
| 135 | 140 |
| 136 /** | 141 /** |
| 137 * Creates [file] (which can either be a [String] or a [File]), and writes | 142 * Creates [file] (which can either be a [String] or a [File]), and writes |
| 138 * [contents] to it. Completes when the file is written and closed. | 143 * [contents] to it. Completes when the file is written and closed. |
| 139 */ | 144 */ |
| 140 Future<File> writeTextFile(file, String contents) { | 145 Future<File> writeTextFile(file, String contents) { |
| 141 file = new File(_getPath(file)); | 146 var path = _getPath(file); |
| 147 file = new File(path); | |
| 148 | |
| 149 // Sanity check: don't spew a huge file. | |
| 150 log.io("Writing ${contents.length} characters to text file $path."); | |
| 151 if (contents.length < 1024 * 1024) { | |
| 152 log.fine("Contents:\n$contents"); | |
| 153 } | |
| 154 | |
| 142 return file.open(FileMode.WRITE).chain((opened) { | 155 return file.open(FileMode.WRITE).chain((opened) { |
| 143 return opened.writeString(contents).chain((ignore) { | 156 return opened.writeString(contents).chain((ignore) { |
| 144 return opened.close().transform((ignore) => file); | 157 return opened.close().transform((_) { |
| 158 log.fine("Wrote text file $path."); | |
| 159 return file; | |
| 160 }); | |
| 145 }); | 161 }); |
| 146 }); | 162 }); |
| 147 } | 163 } |
| 148 | 164 |
| 149 /** | 165 /** |
| 150 * Asynchronously deletes [file], which can be a [String] or a [File]. Returns a | 166 * Asynchronously deletes [file], which can be a [String] or a [File]. Returns a |
| 151 * [Future] that completes when the deletion is done. | 167 * [Future] that completes when the deletion is done. |
| 152 */ | 168 */ |
| 153 Future<File> deleteFile(file) { | 169 Future<File> deleteFile(file) { |
| 154 return new File(_getPath(file)).delete(); | 170 var path = _getPath(file); |
| 171 return log.ioAsync("delete file $path", | |
| 172 new File(path).delete()); | |
| 155 } | 173 } |
| 156 | 174 |
| 157 /// Writes [stream] to a new file at [path], which may be a [String] or a | 175 /// Writes [stream] to a new file at [path], which may be a [String] or a |
| 158 /// [File]. Will replace any file already at that path. Completes when the file | 176 /// [File]. Will replace any file already at that path. Completes when the file |
| 159 /// is done being written. | 177 /// is done being written. |
| 160 Future<File> createFileFromStream(InputStream stream, path) { | 178 Future<File> createFileFromStream(InputStream stream, path) { |
| 161 path = _getPath(path); | 179 path = _getPath(path); |
| 162 | 180 |
| 181 log.io("Creating $path from stream."); | |
| 182 | |
| 163 var completer = new Completer<File>(); | 183 var completer = new Completer<File>(); |
| 164 var file = new File(path); | 184 var file = new File(path); |
| 165 var outputStream = file.openOutputStream(); | 185 var outputStream = file.openOutputStream(); |
| 166 stream.pipe(outputStream); | 186 stream.pipe(outputStream); |
| 167 | 187 |
| 168 outputStream.onClosed = () { | 188 outputStream.onClosed = () { |
| 189 log.fine("Created $path from stream."); | |
| 169 completer.complete(file); | 190 completer.complete(file); |
| 170 }; | 191 }; |
| 171 | 192 |
| 172 // TODO(nweiz): remove this when issue 4061 is fixed. | 193 // TODO(nweiz): remove this when issue 4061 is fixed. |
| 173 var stackTrace; | 194 var stackTrace; |
| 174 try { | 195 try { |
| 175 throw ""; | 196 throw ""; |
| 176 } catch (_, localStackTrace) { | 197 } catch (_, localStackTrace) { |
| 177 stackTrace = localStackTrace; | 198 stackTrace = localStackTrace; |
| 178 } | 199 } |
| 179 | 200 |
| 180 completeError(error) { | 201 completeError(error) { |
| 181 if (!completer.isComplete) completer.completeException(error, stackTrace); | 202 if (!completer.isComplete) { |
| 203 completer.completeException(error, stackTrace); | |
| 204 } else { | |
| 205 log.fine("Got error after stream was closed: $error"); | |
| 206 } | |
| 182 } | 207 } |
| 183 | 208 |
| 184 stream.onError = completeError; | 209 stream.onError = completeError; |
| 185 outputStream.onError = completeError; | 210 outputStream.onError = completeError; |
| 186 | 211 |
| 187 return completer.future; | 212 return completer.future; |
| 188 } | 213 } |
| 189 | 214 |
| 190 /** | 215 /** |
| 191 * Creates a directory [dir]. Returns a [Future] that completes when the | 216 * Creates a directory [dir]. Returns a [Future] that completes when the |
| 192 * directory is created. | 217 * directory is created. |
| 193 */ | 218 */ |
| 194 Future<Directory> createDir(dir) { | 219 Future<Directory> createDir(dir) { |
| 195 dir = _getDirectory(dir); | 220 dir = _getDirectory(dir); |
| 196 return dir.create(); | 221 return log.ioAsync("create directory ${dir.path}", |
| 222 dir.create()); | |
| 197 } | 223 } |
| 198 | 224 |
| 199 /** | 225 /** |
| 200 * Ensures that [path] and all its parent directories exist. If they don't | 226 * Ensures that [path] and all its parent directories exist. If they don't |
| 201 * exist, creates them. Returns a [Future] that completes once all the | 227 * exist, creates them. Returns a [Future] that completes once all the |
| 202 * directories are created. | 228 * directories are created. |
| 203 */ | 229 */ |
| 204 Future<Directory> ensureDir(path) { | 230 Future<Directory> ensureDir(path) { |
| 205 path = _getPath(path); | 231 path = _getPath(path); |
| 232 log.fine("Ensuring directory $path exists."); | |
| 206 if (path == '.') return new Future.immediate(new Directory('.')); | 233 if (path == '.') return new Future.immediate(new Directory('.')); |
| 207 | 234 |
| 208 return dirExists(path).chain((exists) { | 235 return dirExists(path).chain((exists) { |
| 209 if (exists) return new Future.immediate(new Directory(path)); | 236 if (exists) { |
| 237 log.fine("Directory $path already exists."); | |
| 238 return new Future.immediate(new Directory(path)); | |
| 239 } | |
| 240 | |
| 210 return ensureDir(dirname(path)).chain((_) { | 241 return ensureDir(dirname(path)).chain((_) { |
| 211 var completer = new Completer<Directory>(); | 242 var completer = new Completer<Directory>(); |
| 212 var future = createDir(path); | 243 var future = createDir(path); |
| 213 future.handleException((error) { | 244 future.handleException((error) { |
| 214 if (error is! DirectoryIOException) return false; | 245 if (error is! DirectoryIOException) return false; |
| 215 // Error 17 means the directory already exists (or 183 on Windows). | 246 // Error 17 means the directory already exists (or 183 on Windows). |
| 216 if (error.osError.errorCode != 17 && | 247 if (error.osError.errorCode != 17 && |
| 217 error.osError.errorCode != 183) return false; | 248 error.osError.errorCode != 183) { |
| 249 log.fine("Got 'already exists' error when creating directory."); | |
| 250 return false; | |
| 251 } | |
| 218 | 252 |
| 219 completer.complete(_getDirectory(path)); | 253 completer.complete(_getDirectory(path)); |
| 220 return true; | 254 return true; |
| 221 }); | 255 }); |
| 222 future.then(completer.complete); | 256 future.then(completer.complete); |
| 223 return completer.future; | 257 return completer.future; |
| 224 }); | 258 }); |
| 225 }); | 259 }); |
| 226 } | 260 } |
| 227 | 261 |
| 228 /** | 262 /** |
| 229 * Creates a temp directory whose name will be based on [dir] with a unique | 263 * Creates a temp directory whose name will be based on [dir] with a unique |
| 230 * suffix appended to it. If [dir] is not provided, a temp directory will be | 264 * suffix appended to it. If [dir] is not provided, a temp directory will be |
| 231 * created in a platform-dependent temporary location. Returns a [Future] that | 265 * created in a platform-dependent temporary location. Returns a [Future] that |
| 232 * completes when the directory is created. | 266 * completes when the directory is created. |
| 233 */ | 267 */ |
| 234 Future<Directory> createTempDir([dir = '']) { | 268 Future<Directory> createTempDir([dir = '']) { |
| 235 dir = _getDirectory(dir); | 269 dir = _getDirectory(dir); |
| 236 return dir.createTemp(); | 270 return log.ioAsync("create temp directory ${dir.path}", |
| 271 dir.createTemp()); | |
| 237 } | 272 } |
| 238 | 273 |
| 239 /** | 274 /** |
| 240 * Asynchronously recursively deletes [dir], which can be a [String] or a | 275 * Asynchronously recursively deletes [dir], which can be a [String] or a |
| 241 * [Directory]. Returns a [Future] that completes when the deletion is done. | 276 * [Directory]. Returns a [Future] that completes when the deletion is done. |
| 242 */ | 277 */ |
| 243 Future<Directory> deleteDir(dir) { | 278 Future<Directory> deleteDir(dir) { |
| 244 dir = _getDirectory(dir); | 279 dir = _getDirectory(dir); |
| 245 return dir.delete(recursive: true); | 280 return log.ioAsync("delete directory ${dir.path}", |
| 281 dir.delete(recursive: true)); | |
| 246 } | 282 } |
| 247 | 283 |
| 248 /** | 284 /** |
| 249 * Asynchronously lists the contents of [dir], which can be a [String] directory | 285 * Asynchronously lists the contents of [dir], which can be a [String] directory |
| 250 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents | 286 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents |
| 251 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files and | 287 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files and |
| 252 * directories beginning with `.` (defaults to `false`). | 288 * directories beginning with `.` (defaults to `false`). |
| 253 */ | 289 */ |
| 254 Future<List<String>> listDir(dir, | 290 Future<List<String>> listDir(dir, |
| 255 {bool recursive: false, bool includeHiddenFiles: false}) { | 291 {bool recursive: false, bool includeHiddenFiles: false}) { |
| 256 final completer = new Completer<List<String>>(); | 292 final completer = new Completer<List<String>>(); |
| 257 final contents = <String>[]; | 293 final contents = <String>[]; |
| 258 | 294 |
| 259 dir = _getDirectory(dir); | 295 dir = _getDirectory(dir); |
| 296 log.io("Listing directory ${dir.path}."); | |
| 260 var lister = dir.list(recursive: recursive); | 297 var lister = dir.list(recursive: recursive); |
| 261 | 298 |
| 262 lister.onDone = (done) { | 299 lister.onDone = (done) { |
| 263 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile | 300 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile |
| 264 // aren't guaranteed to be called in a certain order. So far, they seem to. | 301 // aren't guaranteed to be called in a certain order. So far, they seem to. |
| 265 if (done) completer.complete(contents); | 302 if (done) { |
| 303 log.fine("Listed directory ${dir.path}:\n" | |
| 304 "${Strings.join(contents, '\n')}"); | |
| 305 completer.complete(contents); | |
| 306 } | |
| 266 }; | 307 }; |
| 267 | 308 |
| 268 // TODO(nweiz): remove this when issue 4061 is fixed. | 309 // TODO(nweiz): remove this when issue 4061 is fixed. |
| 269 var stackTrace; | 310 var stackTrace; |
| 270 try { | 311 try { |
| 271 throw ""; | 312 throw ""; |
| 272 } catch (_, localStackTrace) { | 313 } catch (_, localStackTrace) { |
| 273 stackTrace = localStackTrace; | 314 stackTrace = localStackTrace; |
| 274 } | 315 } |
| 275 | 316 |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 286 return completer.future; | 327 return completer.future; |
| 287 } | 328 } |
| 288 | 329 |
| 289 /** | 330 /** |
| 290 * Asynchronously determines if [dir], which can be a [String] directory path | 331 * Asynchronously determines if [dir], which can be a [String] directory path |
| 291 * or a [Directory], exists on the file system. Returns a [Future] that | 332 * or a [Directory], exists on the file system. Returns a [Future] that |
| 292 * completes with the result. | 333 * completes with the result. |
| 293 */ | 334 */ |
| 294 Future<bool> dirExists(dir) { | 335 Future<bool> dirExists(dir) { |
| 295 dir = _getDirectory(dir); | 336 dir = _getDirectory(dir); |
| 296 return dir.exists(); | 337 return log.ioAsync("Seeing if directory ${dir.path} exists.", |
| 338 dir.exists(), | |
| 339 (exists) => "Directory ${dir.path} " | |
| 340 "${exists ? 'exists' : 'does not exist'}."); | |
| 297 } | 341 } |
| 298 | 342 |
| 299 /** | 343 /** |
| 300 * "Cleans" [dir]. If that directory already exists, it will be deleted. Then a | 344 * "Cleans" [dir]. If that directory already exists, it will be deleted. Then a |
| 301 * new empty directory will be created. Returns a [Future] that completes when | 345 * new empty directory will be created. Returns a [Future] that completes when |
| 302 * the new clean directory is created. | 346 * the new clean directory is created. |
| 303 */ | 347 */ |
| 304 Future<Directory> cleanDir(dir) { | 348 Future<Directory> cleanDir(dir) { |
| 305 return dirExists(dir).chain((exists) { | 349 return dirExists(dir).chain((exists) { |
| 306 if (exists) { | 350 if (exists) { |
| 307 // Delete it first. | 351 // Delete it first. |
| 308 return deleteDir(dir).chain((_) => createDir(dir)); | 352 return deleteDir(dir).chain((_) => createDir(dir)); |
| 309 } else { | 353 } else { |
| 310 // Just create it. | 354 // Just create it. |
| 311 return createDir(dir); | 355 return createDir(dir); |
| 312 } | 356 } |
| 313 }); | 357 }); |
| 314 } | 358 } |
| 315 | 359 |
| 316 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with | 360 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with |
| 317 /// the destination directory. | 361 /// the destination directory. |
| 318 Future<Directory> renameDir(from, String to) { | 362 Future<Directory> renameDir(from, String to) { |
| 319 from = _getDirectory(from); | 363 from = _getDirectory(from); |
| 364 log.io("Renaming directory ${from.path} to $to."); | |
| 320 | 365 |
| 321 if (Platform.operatingSystem != 'windows') return from.rename(to); | 366 if (Platform.operatingSystem != 'windows') { |
| 367 return from.rename(to).transform((dir) { | |
| 368 log.fine("Renamed directory ${from.path} to $to."); | |
| 369 return dir; | |
| 370 }); | |
| 371 } | |
| 322 | 372 |
| 323 // On Windows, we sometimes get failures where the directory is still in use | 373 // On Windows, we sometimes get failures where the directory is still in use |
| 324 // when we try to move it. To be a bit more resilient, we wait and retry a | 374 // when we try to move it. To be a bit more resilient, we wait and retry a |
| 325 // few times. | 375 // few times. |
| 326 var attempts = 0; | 376 var attempts = 0; |
| 327 attemptRename(_) { | 377 attemptRename(_) { |
| 328 attempts++; | 378 attempts++; |
| 329 return from.rename(to).transformException((e) { | 379 return from.rename(to).transform((dir) { |
| 380 log.fine("Renamed directory ${from.path} to $to."); | |
| 381 return dir; | |
| 382 }).transformException((e) { | |
| 330 if (attempts >= 10) { | 383 if (attempts >= 10) { |
| 331 throw 'Could not move directory "${from.path}" to "$to". Gave up ' | 384 throw 'Could not move directory "${from.path}" to "$to". Gave up ' |
| 332 'after $attempts attempts.'; | 385 'after $attempts attempts.'; |
| 333 } | 386 } |
| 334 | 387 |
| 335 // Wait a bit and try again. | 388 // Wait a bit and try again. |
| 389 log.fine("Rename ${from.path} failed, retrying (attempt $attempts)."); | |
| 336 return sleep(500).chain(attemptRename); | 390 return sleep(500).chain(attemptRename); |
| 337 }); | 391 }); |
| 338 | 392 |
| 339 return from; | 393 return from; |
| 340 } | 394 } |
| 341 | 395 |
| 342 return attemptRename(null); | 396 return attemptRename(null); |
| 343 } | 397 } |
| 344 | 398 |
| 345 /** | 399 /** |
| 346 * Creates a new symlink that creates an alias from [from] to [to], both of | 400 * Creates a new symlink that creates an alias from [from] to [to], both of |
| 347 * which can be a [String], [File], or [Directory]. Returns a [Future] which | 401 * which can be a [String], [File], or [Directory]. Returns a [Future] which |
| 348 * completes to the symlink file (i.e. [to]). | 402 * completes to the symlink file (i.e. [to]). |
| 349 */ | 403 */ |
| 350 Future<File> createSymlink(from, to) { | 404 Future<File> createSymlink(from, to) { |
| 351 from = _getPath(from); | 405 from = _getPath(from); |
| 352 to = _getPath(to); | 406 to = _getPath(to); |
| 353 | 407 |
| 408 log.fine("Create symlink $from -> $to."); | |
| 409 | |
| 354 var command = 'ln'; | 410 var command = 'ln'; |
| 355 var args = ['-s', from, to]; | 411 var args = ['-s', from, to]; |
| 356 | 412 |
| 357 if (Platform.operatingSystem == 'windows') { | 413 if (Platform.operatingSystem == 'windows') { |
| 358 // Call mklink on Windows to create an NTFS junction point. Only works on | 414 // Call mklink on Windows to create an NTFS junction point. Only works on |
| 359 // Vista or later. (Junction points are available earlier, but the "mklink" | 415 // Vista or later. (Junction points are available earlier, but the "mklink" |
| 360 // command is not.) I'm using a junction point (/j) here instead of a soft | 416 // command is not.) I'm using a junction point (/j) here instead of a soft |
| 361 // link (/d) because the latter requires some privilege shenanigans that | 417 // link (/d) because the latter requires some privilege shenanigans that |
| 362 // I'm not sure how to specify from the command line. | 418 // I'm not sure how to specify from the command line. |
| 363 command = 'mklink'; | 419 command = 'mklink'; |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 375 * package [from] to [to], both of which can be a [String], [File], or | 431 * package [from] to [to], both of which can be a [String], [File], or |
| 376 * [Directory]. Returns a [Future] which completes to the symlink file (i.e. | 432 * [Directory]. Returns a [Future] which completes to the symlink file (i.e. |
| 377 * [to]). If [from] does not have a `lib` directory, this shows a warning if | 433 * [to]). If [from] does not have a `lib` directory, this shows a warning if |
| 378 * appropriate and then does nothing. | 434 * appropriate and then does nothing. |
| 379 */ | 435 */ |
| 380 Future<File> createPackageSymlink(String name, from, to, | 436 Future<File> createPackageSymlink(String name, from, to, |
| 381 {bool isSelfLink: false}) { | 437 {bool isSelfLink: false}) { |
| 382 // See if the package has a "lib" directory. | 438 // See if the package has a "lib" directory. |
| 383 from = join(from, 'lib'); | 439 from = join(from, 'lib'); |
| 384 return dirExists(from).chain((exists) { | 440 return dirExists(from).chain((exists) { |
| 441 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); | |
| 385 if (exists) return createSymlink(from, to); | 442 if (exists) return createSymlink(from, to); |
| 386 | 443 |
| 387 // It's OK for the self link (i.e. the root package) to not have a lib | 444 // It's OK for the self link (i.e. the root package) to not have a lib |
| 388 // directory since it may just be a leaf application that only has | 445 // directory since it may just be a leaf application that only has |
| 389 // code in bin or web. | 446 // code in bin or web. |
| 390 if (!isSelfLink) { | 447 if (!isSelfLink) { |
| 391 printError( | 448 log.warning('Warning: Package "$name" does not have a "lib" directory so ' |
| 392 'Warning: Package "$name" does not have a "lib" directory so you ' | 449 'you will not be able to import any libraries from it.'); |
| 393 'will not be able to import any libraries from it.'); | |
| 394 } | 450 } |
| 395 | 451 |
| 396 return new Future.immediate(to); | 452 return new Future.immediate(to); |
| 397 }); | 453 }); |
| 398 } | 454 } |
| 399 | 455 |
| 400 /// Given [entry] which may be a [String], [File], or [Directory] relative to | 456 /// Given [entry] which may be a [String], [File], or [Directory] relative to |
| 401 /// the current working directory, returns its full canonicalized path. | 457 /// the current working directory, returns its full canonicalized path. |
| 402 String getFullPath(entry) { | 458 String getFullPath(entry) { |
| 403 var path = _getPath(entry); | 459 var path = _getPath(entry); |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 499 | 555 |
| 500 /// An HTTP client that transforms 40* errors and socket exceptions into more | 556 /// An HTTP client that transforms 40* errors and socket exceptions into more |
| 501 /// user-friendly error messages. | 557 /// user-friendly error messages. |
| 502 class PubHttpClient extends http.BaseClient { | 558 class PubHttpClient extends http.BaseClient { |
| 503 final http.Client _inner; | 559 final http.Client _inner; |
| 504 | 560 |
| 505 PubHttpClient([http.Client inner]) | 561 PubHttpClient([http.Client inner]) |
| 506 : _inner = inner == null ? new http.Client() : inner; | 562 : _inner = inner == null ? new http.Client() : inner; |
| 507 | 563 |
| 508 Future<http.StreamedResponse> send(http.BaseRequest request) { | 564 Future<http.StreamedResponse> send(http.BaseRequest request) { |
| 565 log.io("Sending HTTP request $request."); | |
|
nweiz
2012/12/08 03:09:27
Add a TODO to print the request body when it's ava
Bob Nystrom
2012/12/08 03:13:45
Done.
| |
| 566 | |
| 509 // TODO(nweiz): remove this when issue 4061 is fixed. | 567 // TODO(nweiz): remove this when issue 4061 is fixed. |
| 510 var stackTrace; | 568 var stackTrace; |
| 511 try { | 569 try { |
| 512 throw null; | 570 throw null; |
| 513 } catch (_, localStackTrace) { | 571 } catch (_, localStackTrace) { |
| 514 stackTrace = localStackTrace; | 572 stackTrace = localStackTrace; |
| 515 } | 573 } |
| 516 | 574 |
| 517 // TODO(nweiz): Ideally the timeout would extend to reading from the | 575 // TODO(nweiz): Ideally the timeout would extend to reading from the |
| 518 // response input stream, but until issue 3657 is fixed that's not feasible. | 576 // response input stream, but until issue 3657 is fixed that's not feasible. |
| 519 return timeout(_inner.send(request).chain((streamedResponse) { | 577 return timeout(_inner.send(request).chain((streamedResponse) { |
| 578 log.fine("Got response ${streamedResponse.statusCode} " | |
| 579 "${streamedResponse.reasonPhrase}."); | |
| 580 | |
| 520 var status = streamedResponse.statusCode; | 581 var status = streamedResponse.statusCode; |
| 521 // 401 responses should be handled by the OAuth2 client. It's very | 582 // 401 responses should be handled by the OAuth2 client. It's very |
| 522 // unlikely that they'll be returned by non-OAuth2 requests. | 583 // unlikely that they'll be returned by non-OAuth2 requests. |
| 523 if (status < 400 || status == 401) { | 584 if (status < 400 || status == 401) { |
| 524 return new Future.immediate(streamedResponse); | 585 return new Future.immediate(streamedResponse); |
| 525 } | 586 } |
| 526 | 587 |
| 527 return http.Response.fromStream(streamedResponse).transform((response) { | 588 return http.Response.fromStream(streamedResponse).transform((response) { |
| 528 throw new PubHttpException(response); | 589 throw new PubHttpException(response); |
| 529 }); | 590 }); |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 625 Future<PubProcessResult> runProcess(String executable, List<String> args, | 686 Future<PubProcessResult> runProcess(String executable, List<String> args, |
| 626 {workingDir, Map<String, String> environment}) { | 687 {workingDir, Map<String, String> environment}) { |
| 627 return _doProcess(Process.run, executable, args, workingDir, environment) | 688 return _doProcess(Process.run, executable, args, workingDir, environment) |
| 628 .transform((result) { | 689 .transform((result) { |
| 629 // TODO(rnystrom): Remove this and change to returning one string. | 690 // TODO(rnystrom): Remove this and change to returning one string. |
| 630 List<String> toLines(String output) { | 691 List<String> toLines(String output) { |
| 631 var lines = output.split(NEWLINE_PATTERN); | 692 var lines = output.split(NEWLINE_PATTERN); |
| 632 if (!lines.isEmpty && lines.last == "") lines.removeLast(); | 693 if (!lines.isEmpty && lines.last == "") lines.removeLast(); |
| 633 return lines; | 694 return lines; |
| 634 } | 695 } |
| 635 return new PubProcessResult(toLines(result.stdout), | 696 |
| 697 var pubResult = new PubProcessResult(toLines(result.stdout), | |
| 636 toLines(result.stderr), | 698 toLines(result.stderr), |
| 637 result.exitCode); | 699 result.exitCode); |
| 700 | |
| 701 log.processResult(executable, pubResult); | |
| 702 return pubResult; | |
| 638 }); | 703 }); |
| 639 } | 704 } |
| 640 | 705 |
| 641 /// Spawns the process located at [executable], passing in [args]. Returns a | 706 /// Spawns the process located at [executable], passing in [args]. Returns a |
| 642 /// [Future] that will complete with the [Process] once it's been started. | 707 /// [Future] that will complete with the [Process] once it's been started. |
| 643 /// | 708 /// |
| 644 /// The spawned process will inherit its parent's environment variables. If | 709 /// The spawned process will inherit its parent's environment variables. If |
| 645 /// [environment] is provided, that will be used to augment (not replace) the | 710 /// [environment] is provided, that will be used to augment (not replace) the |
| 646 /// the inherited variables. | 711 /// the inherited variables. |
| 647 Future<Process> startProcess(String executable, List<String> args, | 712 Future<Process> startProcess(String executable, List<String> args, |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 666 final options = new ProcessOptions(); | 731 final options = new ProcessOptions(); |
| 667 if (workingDir != null) { | 732 if (workingDir != null) { |
| 668 options.workingDirectory = _getDirectory(workingDir).path; | 733 options.workingDirectory = _getDirectory(workingDir).path; |
| 669 } | 734 } |
| 670 | 735 |
| 671 if (environment != null) { | 736 if (environment != null) { |
| 672 options.environment = new Map.from(Platform.environment); | 737 options.environment = new Map.from(Platform.environment); |
| 673 environment.forEach((key, value) => options.environment[key] = value); | 738 environment.forEach((key, value) => options.environment[key] = value); |
| 674 } | 739 } |
| 675 | 740 |
| 741 log.process(executable, args); | |
| 742 | |
| 676 return fn(executable, args, options); | 743 return fn(executable, args, options); |
| 677 } | 744 } |
| 678 | 745 |
| 679 /// Closes [response] while ignoring the body of [request]. Returns a Future | 746 /// Closes [response] while ignoring the body of [request]. Returns a Future |
| 680 /// that completes once the response is closed. | 747 /// that completes once the response is closed. |
| 681 /// | 748 /// |
| 682 /// Due to issue 6984, it's necessary to drain the request body before closing | 749 /// Due to issue 6984, it's necessary to drain the request body before closing |
| 683 /// the response. | 750 /// the response. |
| 684 Future closeHttpResponse(HttpRequest request, HttpResponse response) { | 751 Future closeHttpResponse(HttpRequest request, HttpResponse response) { |
| 685 // TODO(nweiz): remove this when issue 4061 is fixed. | 752 // TODO(nweiz): remove this when issue 4061 is fixed. |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 734 | 801 |
| 735 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] | 802 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] |
| 736 /// returned by [fn] completes, the temporary directory and all its contents | 803 /// returned by [fn] completes, the temporary directory and all its contents |
| 737 /// will be deleted. | 804 /// will be deleted. |
| 738 Future withTempDir(Future fn(String path)) { | 805 Future withTempDir(Future fn(String path)) { |
| 739 var tempDir; | 806 var tempDir; |
| 740 var future = createTempDir().chain((dir) { | 807 var future = createTempDir().chain((dir) { |
| 741 tempDir = dir; | 808 tempDir = dir; |
| 742 return fn(tempDir.path); | 809 return fn(tempDir.path); |
| 743 }); | 810 }); |
| 744 future.onComplete((_) => tempDir.delete(recursive: true)); | 811 future.onComplete((_) { |
| 812 log.fine('Cleaning up temp directory ${tempDir.path}.'); | |
| 813 deleteDir(tempDir); | |
| 814 }); | |
| 745 return future; | 815 return future; |
| 746 } | 816 } |
| 747 | 817 |
| 748 /// Tests whether or not the git command-line app is available for use. | 818 /// Tests whether or not the git command-line app is available for use. |
| 749 Future<bool> get isGitInstalled { | 819 Future<bool> get isGitInstalled { |
| 750 if (_isGitInstalledCache != null) { | 820 if (_isGitInstalledCache != null) { |
| 751 // TODO(rnystrom): The sleep is to pump the message queue. Can use | 821 // TODO(rnystrom): The sleep is to pump the message queue. Can use |
| 752 // Future.immediate() when #3356 is fixed. | 822 // Future.immediate() when #3356 is fixed. |
| 753 return sleep(0).transform((_) => _isGitInstalledCache); | 823 return sleep(0).transform((_) => _isGitInstalledCache); |
| 754 } | 824 } |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 807 return completer.future; | 877 return completer.future; |
| 808 } | 878 } |
| 809 | 879 |
| 810 /** | 880 /** |
| 811 * Extracts a `.tar.gz` file from [stream] to [destination], which can be a | 881 * Extracts a `.tar.gz` file from [stream] to [destination], which can be a |
| 812 * directory or a path. Returns whether or not the extraction was successful. | 882 * directory or a path. Returns whether or not the extraction was successful. |
| 813 */ | 883 */ |
| 814 Future<bool> extractTarGz(InputStream stream, destination) { | 884 Future<bool> extractTarGz(InputStream stream, destination) { |
| 815 destination = _getPath(destination); | 885 destination = _getPath(destination); |
| 816 | 886 |
| 887 log.fine("Extracting .tar.gz stream to $destination."); | |
| 888 | |
| 817 if (Platform.operatingSystem == "windows") { | 889 if (Platform.operatingSystem == "windows") { |
| 818 return _extractTarGzWindows(stream, destination); | 890 return _extractTarGzWindows(stream, destination); |
| 819 } | 891 } |
| 820 | 892 |
| 821 var completer = new Completer<int>(); | 893 var completer = new Completer<int>(); |
| 822 var processFuture = Process.start("tar", | 894 var processFuture = Process.start("tar", |
| 823 ["--extract", "--gunzip", "--directory", destination]); | 895 ["--extract", "--gunzip", "--directory", destination]); |
| 824 processFuture.then((process) { | 896 processFuture.then((process) { |
| 825 process.onExit = completer.complete; | 897 process.onExit = completer.complete; |
| 826 stream.pipe(process.stdin); | 898 stream.pipe(process.stdin); |
| 827 process.stdout.pipe(stdout, close: false); | 899 process.stdout.pipe(stdout, close: false); |
| 828 process.stderr.pipe(stderr, close: false); | 900 process.stderr.pipe(stderr, close: false); |
| 829 }); | 901 }); |
| 830 processFuture.handleException((error) { | 902 processFuture.handleException((error) { |
| 831 completer.completeException(error, processFuture.stackTrace); | 903 completer.completeException(error, processFuture.stackTrace); |
| 832 return true; | 904 return true; |
| 833 }); | 905 }); |
| 834 | 906 |
| 835 return completer.future.transform((exitCode) => exitCode == 0); | 907 return completer.future.transform((exitCode) { |
| 908 log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode."); | |
| 909 // TODO(rnystrom): Does anything check this result value? If not, it should | |
| 910 // throw on a bad exit code. | |
| 911 return exitCode == 0; | |
| 912 }); | |
| 836 } | 913 } |
| 837 | 914 |
| 838 Future<bool> _extractTarGzWindows(InputStream stream, String destination) { | 915 Future<bool> _extractTarGzWindows(InputStream stream, String destination) { |
| 839 // TODO(rnystrom): In the repo's history, there is an older implementation of | 916 // TODO(rnystrom): In the repo's history, there is an older implementation of |
| 840 // this that does everything in memory by piping streams directly together | 917 // this that does everything in memory by piping streams directly together |
| 841 // instead of writing out temp files. The code is simpler, but unfortunately, | 918 // instead of writing out temp files. The code is simpler, but unfortunately, |
| 842 // 7zip seems to periodically fail when we invoke it from Dart and tell it to | 919 // 7zip seems to periodically fail when we invoke it from Dart and tell it to |
| 843 // read from stdin instead of a file. Consider resurrecting that version if | 920 // read from stdin instead of a file. Consider resurrecting that version if |
| 844 // we can figure out why it fails. | 921 // we can figure out why it fails. |
| 845 | 922 |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 882 | 959 |
| 883 // Untar the archive into the destination directory. | 960 // Untar the archive into the destination directory. |
| 884 return runProcess(command, ['x', tarFile], workingDir: destination); | 961 return runProcess(command, ['x', tarFile], workingDir: destination); |
| 885 }).chain((result) { | 962 }).chain((result) { |
| 886 if (result.exitCode != 0) { | 963 if (result.exitCode != 0) { |
| 887 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n' | 964 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n' |
| 888 '${Strings.join(result.stdout, "\n")}\n' | 965 '${Strings.join(result.stdout, "\n")}\n' |
| 889 '${Strings.join(result.stderr, "\n")}'; | 966 '${Strings.join(result.stderr, "\n")}'; |
| 890 } | 967 } |
| 891 | 968 |
| 892 // Clean up the temp directory. | 969 log.fine('Clean up 7zip temp directory ${tempDir.path}.'); |
| 893 // TODO(rnystrom): Should also delete this if anything fails. | 970 // TODO(rnystrom): Should also delete this if anything fails. |
| 894 return deleteDir(tempDir); | 971 return deleteDir(tempDir); |
| 895 }).transform((_) => true); | 972 }).transform((_) => true); |
| 896 } | 973 } |
| 897 | 974 |
| 898 /// Create a .tar.gz archive from a list of entries. Each entry can be a | 975 /// Create a .tar.gz archive from a list of entries. Each entry can be a |
| 899 /// [String], [Directory], or [File] object. The root of the archive is | 976 /// [String], [Directory], or [File] object. The root of the archive is |
| 900 /// considered to be [baseDir], which defaults to the current working directory. | 977 /// considered to be [baseDir], which defaults to the current working directory. |
| 901 /// Returns an [InputStream] that will emit the contents of the archive. | 978 /// Returns an [InputStream] that will emit the contents of the archive. |
| 902 InputStream createTarGz(List contents, {baseDir}) { | 979 InputStream createTarGz(List contents, {baseDir}) { |
| 980 log.fine('Creating .tag.gz stream containing:'); | |
| 981 contents.forEach(log.fine); | |
|
nweiz
2012/12/08 03:09:27
Add these to a buffer and print them as one messag
Bob Nystrom
2012/12/08 03:13:45
Done.
| |
| 982 | |
| 903 // TODO(nweiz): Propagate errors to the returned stream (including non-zero | 983 // TODO(nweiz): Propagate errors to the returned stream (including non-zero |
| 904 // exit codes). See issue 3657. | 984 // exit codes). See issue 3657. |
| 905 var stream = new ListInputStream(); | 985 var stream = new ListInputStream(); |
| 906 | 986 |
| 907 if (baseDir == null) baseDir = currentWorkingDir; | 987 if (baseDir == null) baseDir = currentWorkingDir; |
| 908 baseDir = getFullPath(baseDir); | 988 baseDir = getFullPath(baseDir); |
| 909 contents = contents.map((entry) { | 989 contents = contents.map((entry) { |
| 910 entry = getFullPath(entry); | 990 entry = getFullPath(entry); |
| 911 if (!isBeneath(entry, baseDir)) { | 991 if (!isBeneath(entry, baseDir)) { |
| 912 throw 'Entry $entry is not inside $baseDir.'; | 992 throw 'Entry $entry is not inside $baseDir.'; |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 958 | 1038 |
| 959 /** | 1039 /** |
| 960 * Exception thrown when an HTTP operation fails. | 1040 * Exception thrown when an HTTP operation fails. |
| 961 */ | 1041 */ |
| 962 class PubHttpException implements Exception { | 1042 class PubHttpException implements Exception { |
| 963 final http.Response response; | 1043 final http.Response response; |
| 964 | 1044 |
| 965 const PubHttpException(this.response); | 1045 const PubHttpException(this.response); |
| 966 | 1046 |
| 967 String toString() => 'HTTP error ${response.statusCode}: ' | 1047 String toString() => 'HTTP error ${response.statusCode}: ' |
| 968 '${response.reasonPhrase}'; | 1048 '${response.reasonPhrase}'; |
| 969 } | 1049 } |
| 970 | 1050 |
| 971 /** | 1051 /** |
| 972 * Exception thrown when an operation times out. | 1052 * Exception thrown when an operation times out. |
| 973 */ | 1053 */ |
| 974 class TimeoutException implements Exception { | 1054 class TimeoutException implements Exception { |
| 975 final String message; | 1055 final String message; |
| 976 | 1056 |
| 977 const TimeoutException(this.message); | 1057 const TimeoutException(this.message); |
| 978 | 1058 |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1029 return new Directory(entry); | 1109 return new Directory(entry); |
| 1030 } | 1110 } |
| 1031 | 1111 |
| 1032 /** | 1112 /** |
| 1033 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 1113 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
| 1034 */ | 1114 */ |
| 1035 Uri _getUri(uri) { | 1115 Uri _getUri(uri) { |
| 1036 if (uri is Uri) return uri; | 1116 if (uri is Uri) return uri; |
| 1037 return new Uri.fromString(uri); | 1117 return new Uri.fromString(uri); |
| 1038 } | 1118 } |
| OLD | NEW |