| 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'; |
| (...skipping 13 matching lines...) Expand all Loading... |
| 24 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); | 24 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); |
| 25 | 25 |
| 26 /** | 26 /** |
| 27 * Prints the given string to `stderr` on its own line. | 27 * Prints the given string to `stderr` on its own line. |
| 28 */ | 28 */ |
| 29 void printError(value) { | 29 void printError(value) { |
| 30 stderr.writeString(value.toString()); | 30 stderr.writeString(value.toString()); |
| 31 stderr.writeString('\n'); | 31 stderr.writeString('\n'); |
| 32 } | 32 } |
| 33 | 33 |
| 34 |
| 34 /** | 35 /** |
| 35 * Joins a number of path string parts into a single path. Handles | 36 * Joins a number of path string parts into a single path. Handles |
| 36 * platform-specific path separators. Parts can be [String], [Directory], or | 37 * platform-specific path separators. Parts can be [String], [Directory], or |
| 37 * [File] objects. | 38 * [File] objects. |
| 38 */ | 39 */ |
| 39 String join(part1, [part2, part3, part4]) { | 40 String join(part1, [part2, part3, part4]) { |
| 40 final parts = _getPath(part1).replaceAll('\\', '/').split('/'); | 41 final parts = _sanitizePath(part1).split('/'); |
| 41 | 42 |
| 42 for (final part in [part2, part3, part4]) { | 43 for (final part in [part2, part3, part4]) { |
| 43 if (part == null) continue; | 44 if (part == null) continue; |
| 44 | 45 |
| 45 for (final piece in _getPath(part).split('/')) { | 46 for (final piece in _getPath(part).split('/')) { |
| 46 if (piece == '..' && parts.length > 0 && | 47 if (piece == '..' && parts.length > 0 && |
| 47 parts.last != '.' && parts.last != '..') { | 48 parts.last != '.' && parts.last != '..') { |
| 48 parts.removeLast(); | 49 parts.removeLast(); |
| 49 } else if (piece != '') { | 50 } else if (piece != '') { |
| 50 if (parts.length > 0 && parts.last == '.') { | 51 if (parts.length > 0 && parts.last == '.') { |
| 51 parts.removeLast(); | 52 parts.removeLast(); |
| 52 } | 53 } |
| 53 parts.add(piece); | 54 parts.add(piece); |
| 54 } | 55 } |
| 55 } | 56 } |
| 56 } | 57 } |
| 57 | 58 |
| 58 return Strings.join(parts, Platform.pathSeparator); | 59 return Strings.join(parts, Platform.pathSeparator); |
| 59 } | 60 } |
| 60 | 61 |
| 61 /** | 62 /** |
| 62 * Gets the basename, the file name without any leading directory path, for | 63 * Gets the basename, the file name without any leading directory path, for |
| 63 * [file], which can either be a [String], [File], or [Directory]. | 64 * [file], which can either be a [String], [File], or [Directory]. |
| 64 */ | 65 */ |
| 65 // TODO(rnystrom): Copied from file_system (so that we don't have to add | 66 // TODO(rnystrom): Copied from file_system (so that we don't have to add |
| 66 // file_system to the SDK). Should unify. | 67 // file_system to the SDK). Should unify. |
| 67 String basename(file) { | 68 String basename(file) { |
| 68 file = _getPath(file).replaceAll('\\', '/'); | 69 file = _sanitizePath(file); |
| 69 | 70 |
| 70 int lastSlash = file.lastIndexOf('/', file.length); | 71 int lastSlash = file.lastIndexOf('/', file.length); |
| 71 if (lastSlash == -1) { | 72 if (lastSlash == -1) { |
| 72 return file; | 73 return file; |
| 73 } else { | 74 } else { |
| 74 return file.substring(lastSlash + 1); | 75 return file.substring(lastSlash + 1); |
| 75 } | 76 } |
| 76 } | 77 } |
| 77 | 78 |
| 78 /** | 79 /** |
| 79 * Gets the the leading directory path for [file], which can either be a | 80 * Gets the the leading directory path for [file], which can either be a |
| 80 * [String], [File], or [Directory]. | 81 * [String], [File], or [Directory]. |
| 81 */ | 82 */ |
| 82 // TODO(nweiz): Copied from file_system (so that we don't have to add | 83 // TODO(nweiz): Copied from file_system (so that we don't have to add |
| 83 // file_system to the SDK). Should unify. | 84 // file_system to the SDK). Should unify. |
| 84 String dirname(file) { | 85 String dirname(file) { |
| 85 file = _getPath(file).replaceAll('\\', '/'); | 86 file = _sanitizePath(file); |
| 86 | 87 |
| 87 int lastSlash = file.lastIndexOf('/', file.length); | 88 int lastSlash = file.lastIndexOf('/', file.length); |
| 88 if (lastSlash == -1) { | 89 if (lastSlash == -1) { |
| 89 return '.'; | 90 return '.'; |
| 90 } else { | 91 } else { |
| 91 return file.substring(0, lastSlash); | 92 return file.substring(0, lastSlash); |
| 92 } | 93 } |
| 93 } | 94 } |
| 94 | 95 |
| 96 /// Returns whether or not [entry] is nested somewhere within [dir]. This just |
| 97 /// performs a path comparison; it doesn't look at the actual filesystem. |
| 98 bool isBeneath(entry, dir) => |
| 99 _sanitizePath(entry).startsWith('${_sanitizePath(dir)}/'); |
| 100 |
| 95 /** | 101 /** |
| 96 * Asynchronously determines if [path], which can be a [String] file path, a | 102 * Asynchronously determines if [path], which can be a [String] file path, a |
| 97 * [File], or a [Directory] exists on the file system. Returns a [Future] that | 103 * [File], or a [Directory] exists on the file system. Returns a [Future] that |
| 98 * completes with the result. | 104 * completes with the result. |
| 99 */ | 105 */ |
| 100 Future<bool> exists(path) { | 106 Future<bool> exists(path) { |
| 101 path = _getPath(path); | 107 path = _getPath(path); |
| 102 return Futures.wait([fileExists(path), dirExists(path)]).transform((results) { | 108 return Futures.wait([fileExists(path), dirExists(path)]).transform((results) { |
| 103 return results[0] || results[1]; | 109 return results[0] || results[1]; |
| 104 }); | 110 }); |
| (...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 221 * [Directory]. Returns a [Future] that completes when the deletion is done. | 227 * [Directory]. Returns a [Future] that completes when the deletion is done. |
| 222 */ | 228 */ |
| 223 Future<Directory> deleteDir(dir) { | 229 Future<Directory> deleteDir(dir) { |
| 224 dir = _getDirectory(dir); | 230 dir = _getDirectory(dir); |
| 225 return dir.delete(recursive: true); | 231 return dir.delete(recursive: true); |
| 226 } | 232 } |
| 227 | 233 |
| 228 /** | 234 /** |
| 229 * Asynchronously lists the contents of [dir], which can be a [String] directory | 235 * Asynchronously lists the contents of [dir], which can be a [String] directory |
| 230 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents | 236 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents |
| 231 * (defaults to `false`). If [includeSpecialFiles] is `true`, includes | 237 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files |
| 232 * hidden `.DS_Store` files (defaults to `false`, other hidden files may be | 238 * beginning with `.` (defaults to `false`). |
| 233 * omitted later). | |
| 234 */ | 239 */ |
| 235 Future<List<String>> listDir(dir, | 240 Future<List<String>> listDir(dir, |
| 236 [bool recursive = false, bool includeSpecialFiles = false]) { | 241 {bool recursive: false, bool includeHiddenFiles: false}) { |
| 237 final completer = new Completer<List<String>>(); | 242 final completer = new Completer<List<String>>(); |
| 238 final contents = <String>[]; | 243 final contents = <String>[]; |
| 239 | 244 |
| 240 dir = _getDirectory(dir); | 245 dir = _getDirectory(dir); |
| 241 var lister = dir.list(recursive: recursive); | 246 var lister = dir.list(recursive: recursive); |
| 242 | 247 |
| 243 lister.onDone = (done) { | 248 lister.onDone = (done) { |
| 244 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile | 249 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile |
| 245 // aren't guaranteed to be called in a certain order. So far, they seem to. | 250 // aren't guaranteed to be called in a certain order. So far, they seem to. |
| 246 if (done) completer.complete(contents); | 251 if (done) completer.complete(contents); |
| 247 }; | 252 }; |
| 248 | 253 |
| 249 lister.onError = (error) => completer.completeException(error); | 254 lister.onError = (error) => completer.completeException(error); |
| 250 lister.onDir = (file) => contents.add(file); | 255 lister.onDir = (file) => contents.add(file); |
| 251 lister.onFile = (file) { | 256 lister.onFile = (file) { |
| 252 if (!includeSpecialFiles) { | 257 if (!includeHiddenFiles && basename(file).startsWith('.')) return; |
| 253 if (basename(file) == '.DS_Store') return; | |
| 254 } | |
| 255 contents.add(file); | 258 contents.add(file); |
| 256 }; | 259 }; |
| 257 | 260 |
| 258 return completer.future; | 261 return completer.future; |
| 259 } | 262 } |
| 260 | 263 |
| 261 /** | 264 /** |
| 262 * Asynchronously determines if [dir], which can be a [String] directory path | 265 * Asynchronously determines if [dir], which can be a [String] directory path |
| 263 * or a [Directory], exists on the file system. Returns a [Future] that | 266 * or a [Directory], exists on the file system. Returns a [Future] that |
| 264 * completes with the result. | 267 * completes with the result. |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 365 .toNativePath(); | 368 .toNativePath(); |
| 366 } | 369 } |
| 367 | 370 |
| 368 /// Resolves [path] relative to the location of pub.dart. | 371 /// Resolves [path] relative to the location of pub.dart. |
| 369 String relativeToPub(String path) { | 372 String relativeToPub(String path) { |
| 370 var scriptPath = new File(new Options().script).fullPathSync(); | 373 var scriptPath = new File(new Options().script).fullPathSync(); |
| 371 var scriptDir = new Path.fromNative(scriptPath).directoryPath; | 374 var scriptDir = new Path.fromNative(scriptPath).directoryPath; |
| 372 return scriptDir.append(path).canonicalize().toNativePath(); | 375 return scriptDir.append(path).canonicalize().toNativePath(); |
| 373 } | 376 } |
| 374 | 377 |
| 378 /// A StringInputStream reading from stdin. |
| 379 final _stringStdin = new StringInputStream(stdin); |
| 380 |
| 381 /// Returns a single line read from a [StringInputStream]. By default, reads |
| 382 /// from stdin. |
| 383 /// |
| 384 /// A [StringInputStream] passed to this should have no callbacks registered. |
| 385 Future<String> readLine([StringInputStream stream]) { |
| 386 if (stream == null) stream = _stringStdin; |
| 387 if (stream.closed) return new Future.immediate(''); |
| 388 void removeCallbacks() { |
| 389 stream.onClosed = null; |
| 390 stream.onLine = null; |
| 391 stream.onError = null; |
| 392 } |
| 393 |
| 394 var completer = new Completer(); |
| 395 stream.onClosed = () { |
| 396 removeCallbacks(); |
| 397 completer.complete(''); |
| 398 }; |
| 399 |
| 400 stream.onLine = () { |
| 401 removeCallbacks(); |
| 402 completer.complete(stream.readLine()); |
| 403 }; |
| 404 |
| 405 stream.onError = (e) { |
| 406 removeCallbacks(); |
| 407 completer.completeException(e); |
| 408 }; |
| 409 |
| 410 return completer.future; |
| 411 } |
| 412 |
| 375 // TODO(nweiz): make this configurable | 413 // TODO(nweiz): make this configurable |
| 376 /** | 414 /** |
| 377 * The amount of time in milliseconds to allow HTTP requests before assuming | 415 * The amount of time in milliseconds to allow HTTP requests before assuming |
| 378 * they've failed. | 416 * they've failed. |
| 379 */ | 417 */ |
| 380 final HTTP_TIMEOUT = 30 * 1000; | 418 final HTTP_TIMEOUT = 30 * 1000; |
| 381 | 419 |
| 382 /** | 420 /** |
| 383 * Opens an input stream for a HTTP GET request to [uri], which may be a | 421 * Opens an input stream for a HTTP GET request to [uri], which may be a |
| 384 * [String] or [Uri]. | 422 * [String] or [Uri]. |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 425 return completer.future; | 463 return completer.future; |
| 426 } | 464 } |
| 427 | 465 |
| 428 /** | 466 /** |
| 429 * Opens an input stream for a HTTP GET request to [uri], which may be a | 467 * Opens an input stream for a HTTP GET request to [uri], which may be a |
| 430 * [String] or [Uri]. Completes with the result of the request as a String. | 468 * [String] or [Uri]. Completes with the result of the request as a String. |
| 431 */ | 469 */ |
| 432 Future<String> httpGetString(uri) { | 470 Future<String> httpGetString(uri) { |
| 433 var future = httpGet(uri).chain((stream) => consumeInputStream(stream)) | 471 var future = httpGet(uri).chain((stream) => consumeInputStream(stream)) |
| 434 .transform((bytes) => new String.fromCharCodes(bytes)); | 472 .transform((bytes) => new String.fromCharCodes(bytes)); |
| 435 return timeout(future, HTTP_TIMEOUT, 'Timed out while fetching URL "$uri".'); | 473 return timeout(future, HTTP_TIMEOUT, 'fetching URL "$uri"'); |
| 436 } | 474 } |
| 437 | 475 |
| 438 /** | 476 /** |
| 439 * Takes all input from [source] and writes it to [sink]. | 477 * Takes all input from [source] and writes it to [sink]. |
| 440 * | 478 * |
| 441 * [onClosed] is called when [source] is closed. | 479 * [onClosed] is called when [source] is closed. |
| 442 */ | 480 */ |
| 443 void pipeInputToInput(InputStream source, ListInputStream sink, | 481 void pipeInputToInput(InputStream source, ListInputStream sink, |
| 444 [void onClosed()]) { | 482 [void onClosed()]) { |
| 445 source.onClosed = () { | 483 source.onClosed = () { |
| 446 sink.markEndOfStream(); | 484 sink.markEndOfStream(); |
| 447 if (onClosed != null) onClosed(); | 485 if (onClosed != null) onClosed(); |
| 448 }; | 486 }; |
| 449 source.onData = () => sink.write(source.read()); | 487 source.onData = () => sink.write(source.read()); |
| 450 // TODO(nweiz): propagate this error to the sink. See issue 3657. | 488 // TODO(nweiz): propagate this error to the sink. See issue 3657. |
| 451 source.onError = (e) { throw e; }; | 489 source.onError = (e) { throw e; }; |
| 452 } | 490 } |
| 453 | 491 |
| 454 /** | 492 /** |
| 455 * Buffers all input from an InputStream and returns it as a future. | 493 * Buffers all input from an InputStream and returns it as a future. |
| 456 */ | 494 */ |
| 457 Future<List<int>> consumeInputStream(InputStream stream) { | 495 Future<List<int>> consumeInputStream(InputStream stream) { |
| 496 if (stream.closed) return new Future.immediate(<int>[]); |
| 497 |
| 458 var completer = new Completer<List<int>>(); | 498 var completer = new Completer<List<int>>(); |
| 459 var buffer = <int>[]; | 499 var buffer = <int>[]; |
| 460 stream.onClosed = () => completer.complete(buffer); | 500 stream.onClosed = () => completer.complete(buffer); |
| 461 stream.onData = () => buffer.addAll(stream.read()); | 501 stream.onData = () => buffer.addAll(stream.read()); |
| 462 stream.onError = (e) => completer.completeException(e); | 502 stream.onError = (e) => completer.completeException(e); |
| 463 return completer.future; | 503 return completer.future; |
| 464 } | 504 } |
| 465 | 505 |
| 506 /// Buffers all input from a StringInputStream and returns it as a future. |
| 507 Future<String> consumeStringInputStream(StringInputStream stream) { |
| 508 if (stream.closed) return new Future.immediate(''); |
| 509 |
| 510 var completer = new Completer<String>(); |
| 511 var buffer = new StringBuffer(); |
| 512 stream.onClosed = () => completer.complete(buffer.toString()); |
| 513 stream.onData = () => buffer.add(stream.read()); |
| 514 stream.onError = (e) => completer.completeException(e); |
| 515 return completer.future; |
| 516 } |
| 517 |
| 466 /// Spawns and runs the process located at [executable], passing in [args]. | 518 /// Spawns and runs the process located at [executable], passing in [args]. |
| 467 /// Returns a [Future] that will complete the results of the process after it | 519 /// Returns a [Future] that will complete with the results of the process after |
| 468 /// has ended. | 520 /// it has ended. |
| 469 /// | 521 /// |
| 470 /// The spawned process will inherit its parent's environment variables. If | 522 /// The spawned process will inherit its parent's environment variables. If |
| 471 /// [environment] is provided, that will be used to augment (not replace) the | 523 /// [environment] is provided, that will be used to augment (not replace) the |
| 472 /// the inherited variables. | 524 /// the inherited variables. |
| 525 Future<PubProcessResult> runProcess(String executable, List<String> args, |
| 526 {workingDir, Map<String, String> environment}) { |
| 527 return _doProcess(Process.run, executable, args, workingDir, environment) |
| 528 .transform((result) { |
| 529 // TODO(rnystrom): Remove this and change to returning one string. |
| 530 List<String> toLines(String output) { |
| 531 var lines = output.split(NEWLINE_PATTERN); |
| 532 if (!lines.isEmpty && lines.last == "") lines.removeLast(); |
| 533 return lines; |
| 534 } |
| 535 return new PubProcessResult(toLines(result.stdout), |
| 536 toLines(result.stderr), |
| 537 result.exitCode); |
| 538 }); |
| 539 } |
| 540 |
| 541 /// Spawns the process located at [executable], passing in [args]. Returns a |
| 542 /// [Future] that will complete with the [Process] once it's been started. |
| 473 /// | 543 /// |
| 474 /// If [pipeStdout] and/or [pipeStderr] are set, all output from the | 544 /// The spawned process will inherit its parent's environment variables. If |
| 475 /// subprocess's output streams are sent to the parent process's output streams. | 545 /// [environment] is provided, that will be used to augment (not replace) the |
| 476 /// Output from piped streams won't be available in the result object. | 546 /// the inherited variables. |
| 477 Future<PubProcessResult> runProcess(String executable, List<String> args, | 547 Future<Process> startProcess(String executable, List<String> args, |
| 478 {workingDir, Map<String, String> environment, bool pipeStdout: false, | 548 {workingDir, Map<String, String> environment}) => |
| 479 bool pipeStderr: false}) { | 549 _doProcess(Process.start, executable, args, workingDir, environment); |
| 480 int exitCode; | |
| 481 | 550 |
| 551 /// Calls [fn] with appropriately modified arguments. [fn] should have the same |
| 552 /// signature as [Process.start], except that the returned [Future] may have a |
| 553 /// type other than [Process]. |
| 554 Future _doProcess(Function fn, String executable, List<String> args, workingDir, |
| 555 Map<String, String> environment) { |
| 482 // TODO(rnystrom): Should dart:io just handle this? | 556 // TODO(rnystrom): Should dart:io just handle this? |
| 483 // Spawning a process on Windows will not look for the executable in the | 557 // Spawning a process on Windows will not look for the executable in the |
| 484 // system path. So, if executable looks like it needs that (i.e. it doesn't | 558 // system path. So, if executable looks like it needs that (i.e. it doesn't |
| 485 // have any path separators in it), then spawn it through a shell. | 559 // have any path separators in it), then spawn it through a shell. |
| 486 if ((Platform.operatingSystem == "windows") && | 560 if ((Platform.operatingSystem == "windows") && |
| 487 (executable.indexOf('\\') == -1)) { | 561 (executable.indexOf('\\') == -1)) { |
| 488 args = flatten(["/c", executable, args]); | 562 args = flatten(["/c", executable, args]); |
| 489 executable = "cmd"; | 563 executable = "cmd"; |
| 490 } | 564 } |
| 491 | 565 |
| 492 final options = new ProcessOptions(); | 566 final options = new ProcessOptions(); |
| 493 if (workingDir != null) { | 567 if (workingDir != null) { |
| 494 options.workingDirectory = _getDirectory(workingDir).path; | 568 options.workingDirectory = _getDirectory(workingDir).path; |
| 495 } | 569 } |
| 496 | 570 |
| 497 if (environment != null) { | 571 if (environment != null) { |
| 498 options.environment = new Map.from(Platform.environment); | 572 options.environment = new Map.from(Platform.environment); |
| 499 environment.forEach((key, value) => options.environment[key] = value); | 573 environment.forEach((key, value) => options.environment[key] = value); |
| 500 } | 574 } |
| 501 | 575 |
| 502 var future = Process.run(executable, args, options); | 576 return fn(executable, args, options); |
| 503 return future.transform((result) { | |
| 504 // TODO(rnystrom): Remove this and change to returning one string. | |
| 505 List<String> toLines(String output) { | |
| 506 var lines = output.split(NEWLINE_PATTERN); | |
| 507 if (!lines.isEmpty && lines.last == "") lines.removeLast(); | |
| 508 return lines; | |
| 509 } | |
| 510 return new PubProcessResult(toLines(result.stdout), | |
| 511 toLines(result.stderr), | |
| 512 result.exitCode); | |
| 513 }); | |
| 514 } | 577 } |
| 515 | 578 |
| 516 /** | 579 /** |
| 517 * Wraps [input] to provide a timeout. If [input] completes before | 580 * Wraps [input] to provide a timeout. If [input] completes before |
| 518 * [milliseconds] have passed, then the return value completes in the same way. | 581 * [milliseconds] have passed, then the return value completes in the same way. |
| 519 * However, if [milliseconds] pass before [input] has completed, it completes | 582 * However, if [milliseconds] pass before [input] has completed, it completes |
| 520 * with a [TimeoutException] with [message]. | 583 * with a [TimeoutException] with [description] (which should be a fragment |
| 584 * describing the action that timed out). |
| 521 * | 585 * |
| 522 * Note that timing out will not cancel the asynchronous operation behind | 586 * Note that timing out will not cancel the asynchronous operation behind |
| 523 * [input]. | 587 * [input]. |
| 524 */ | 588 */ |
| 525 Future timeout(Future input, int milliseconds, String message) { | 589 Future timeout(Future input, int milliseconds, String description) { |
| 526 var completer = new Completer(); | 590 var completer = new Completer(); |
| 527 var timer = new Timer(milliseconds, (_) { | 591 var timer = new Timer(milliseconds, (_) { |
| 528 if (completer.future.isComplete) return; | 592 if (completer.future.isComplete) return; |
| 529 completer.completeException(new TimeoutException(message)); | 593 completer.completeException(new TimeoutException( |
| 594 'Timed out while $description.')); |
| 530 }); | 595 }); |
| 531 input.handleException((e) { | 596 input.handleException((e) { |
| 532 if (completer.future.isComplete) return false; | 597 if (completer.future.isComplete) return false; |
| 533 timer.cancel(); | 598 timer.cancel(); |
| 534 completer.completeException(e); | 599 completer.completeException(e); |
| 535 return true; | 600 return true; |
| 536 }); | 601 }); |
| 537 input.then((value) { | 602 input.then((value) { |
| 538 if (completer.future.isComplete) return; | 603 if (completer.future.isComplete) return; |
| 539 timer.cancel(); | 604 timer.cancel(); |
| (...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 697 '${Strings.join(result.stdout, "\n")}\n' | 762 '${Strings.join(result.stdout, "\n")}\n' |
| 698 '${Strings.join(result.stderr, "\n")}'; | 763 '${Strings.join(result.stderr, "\n")}'; |
| 699 } | 764 } |
| 700 | 765 |
| 701 // Clean up the temp directory. | 766 // Clean up the temp directory. |
| 702 // TODO(rnystrom): Should also delete this if anything fails. | 767 // TODO(rnystrom): Should also delete this if anything fails. |
| 703 return deleteDir(tempDir); | 768 return deleteDir(tempDir); |
| 704 }).transform((_) => true); | 769 }).transform((_) => true); |
| 705 } | 770 } |
| 706 | 771 |
| 772 /// Create a .tar.gz archive from a list of entries. Each entry can be a |
| 773 /// [String], [Directory], or [File] object. The root of the archive is |
| 774 /// considered to be [baseDir], which defaults to the current working directory. |
| 775 /// Returns an [InputStream] that will emit the contents of the archive. |
| 776 InputStream createTarGz(List contents, {baseDir}) { |
| 777 // TODO(nweiz): Propagate errors to the returned stream (including non-zero |
| 778 // exit codes). See issue 3657. |
| 779 var stream = new ListInputStream(); |
| 780 |
| 781 if (baseDir == null) baseDir = currentWorkingDir; |
| 782 baseDir = getFullPath(baseDir); |
| 783 contents = contents.map((entry) { |
| 784 entry = getFullPath(entry); |
| 785 if (!isBeneath(entry, baseDir)) { |
| 786 throw 'Entry $entry is not inside $baseDir.'; |
| 787 } |
| 788 return new Path(entry).relativeTo(new Path(baseDir)).toNativePath(); |
| 789 }); |
| 790 |
| 791 if (Platform.operatingSystem != "windows") { |
| 792 var args = ["--create", "--gzip", "--directory", baseDir]; |
| 793 args.addAll(contents.map(_getPath)); |
| 794 // TODO(nweiz): It's possible that enough command-line arguments will make |
| 795 // the process choke, so at some point we should save the arguments to a |
| 796 // file and pass them in via --files-from for tar and -i@filename for 7zip. |
| 797 startProcess("tar", args).then((process) { |
| 798 pipeInputToInput(process.stdout, stream); |
| 799 process.stderr.pipe(stderr, close: false); |
| 800 }); |
| 801 return stream; |
| 802 } |
| 803 |
| 804 withTempDir((tempDir) { |
| 805 // Create the tar file. |
| 806 var tarFile = join(tempDir, "intermediate.tar"); |
| 807 var args = ["a", "-w$baseDir", tarFile]; |
| 808 args.addAll(contents.map((entry) => '-i!"$entry"')); |
| 809 |
| 810 // Note: This line of code gets munged by create_sdk.py to be the correct |
| 811 // relative path to 7zip in the SDK. |
| 812 var pathTo7zip = '../../third_party/7zip/7za.exe'; |
| 813 var command = relativeToPub(pathTo7zip); |
| 814 |
| 815 return runProcess(command, args).chain((_) { |
| 816 // GZIP it. 7zip doesn't support doing both as a single operation. Send |
| 817 // the output to stdout. |
| 818 args = ["a", "not used", "-so", tarFile]; |
| 819 return startProcess(command, args); |
| 820 }).transform((process) { |
| 821 pipeInputToInput(process.stdout, stream); |
| 822 process.stderr.pipe(stderr, close: false); |
| 823 }); |
| 824 }); |
| 825 return stream; |
| 826 } |
| 827 |
| 707 /** | 828 /** |
| 708 * Exception thrown when an HTTP operation fails. | 829 * Exception thrown when an HTTP operation fails. |
| 709 */ | 830 */ |
| 710 class PubHttpException implements Exception { | 831 class PubHttpException implements Exception { |
| 711 final int statusCode; | 832 final int statusCode; |
| 712 final String reason; | 833 final String reason; |
| 713 | 834 |
| 714 const PubHttpException(this.statusCode, this.reason); | 835 const PubHttpException(this.statusCode, this.reason); |
| 715 | 836 |
| 716 String toString() => 'HTTP error $statusCode: $reason'; | 837 String toString() => 'HTTP error $statusCode: $reason'; |
| (...skipping 28 matching lines...) Expand all Loading... |
| 745 * or be a [File] or [Directory]. Allows working generically with "file-like" | 866 * or be a [File] or [Directory]. Allows working generically with "file-like" |
| 746 * objects. | 867 * objects. |
| 747 */ | 868 */ |
| 748 String _getPath(entry) { | 869 String _getPath(entry) { |
| 749 if (entry is String) return entry; | 870 if (entry is String) return entry; |
| 750 if (entry is File) return entry.name; | 871 if (entry is File) return entry.name; |
| 751 if (entry is Directory) return entry.path; | 872 if (entry is Directory) return entry.path; |
| 752 throw 'Entry $entry is not a supported type.'; | 873 throw 'Entry $entry is not a supported type.'; |
| 753 } | 874 } |
| 754 | 875 |
| 876 /// Gets the path string for [entry] as in [_getPath], but normalizes |
| 877 /// backslashes to forward slashes on Windows. |
| 878 String _sanitizePath(entry) { |
| 879 entry = _getPath(entry); |
| 880 if (Platform.operatingSystem == 'windows') { |
| 881 entry = entry.replaceAll('\\', '/'); |
| 882 } |
| 883 return entry; |
| 884 } |
| 885 |
| 755 /** | 886 /** |
| 756 * Gets a [Directory] for [entry], which can either already be one, or be a | 887 * Gets a [Directory] for [entry], which can either already be one, or be a |
| 757 * [String]. | 888 * [String]. |
| 758 */ | 889 */ |
| 759 Directory _getDirectory(entry) { | 890 Directory _getDirectory(entry) { |
| 760 if (entry is Directory) return entry; | 891 if (entry is Directory) return entry; |
| 761 return new Directory(entry); | 892 return new Directory(entry); |
| 762 } | 893 } |
| 763 | 894 |
| 764 /** | 895 /** |
| 765 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 896 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
| 766 */ | 897 */ |
| 767 Uri _getUri(uri) { | 898 Uri _getUri(uri) { |
| 768 if (uri is Uri) return uri; | 899 if (uri is Uri) return uri; |
| 769 return new Uri.fromString(uri); | 900 return new Uri.fromString(uri); |
| 770 } | 901 } |
| OLD | NEW |