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 /// Helper functionality to make working with IO easier. | 5 /// Helper functionality to make working with IO easier. |
| 6 library io; | 6 library io; |
| 7 | 7 |
| 8 import 'dart:async'; | 8 import 'dart:async'; |
| 9 import 'dart:io'; | 9 import 'dart:io'; |
| 10 import 'dart:isolate'; | 10 import 'dart:isolate'; |
| 11 import 'dart:json'; | 11 import 'dart:json'; |
| 12 import 'dart:uri'; | 12 import 'dart:uri'; |
| 13 | 13 |
| 14 import '../../pkg/path/lib/path.dart' as path; | 14 import '../../pkg/path/lib/path.dart' as path; |
| 15 import '../../pkg/http/lib/http.dart' show ByteStream; | 15 import '../../pkg/http/lib/http.dart' show ByteStream; |
| 16 import 'error_group.dart'; | 16 import 'error_group.dart'; |
| 17 import 'exit_codes.dart' as exit_codes; | 17 import 'exit_codes.dart' as exit_codes; |
| 18 import 'log.dart' as log; | 18 import 'log.dart' as log; |
| 19 import 'utils.dart'; | 19 import 'utils.dart'; |
| 20 | 20 |
| 21 export '../../pkg/http/lib/http.dart' show ByteStream; | 21 export '../../pkg/http/lib/http.dart' show ByteStream; |
| 22 | 22 |
| 23 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); | 23 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); |
| 24 | 24 |
| 25 /// Joins a number of path string parts into a single path. Handles | 25 /// Joins a number of path string parts into a single path. Handles |
| 26 /// platform-specific path separators. Parts can be [String], [Directory], or | 26 /// platform-specific path separators. Parts can be [String], [Directory], or |
| 27 /// [File] objects. | 27 /// [File] objects. |
| 28 String join(part1, [part2, part3, part4, part5, part6, part7, part8]) { | 28 String join(part1, [part2, part3, part4, part5, part6, part7, part8]) { |
|
nweiz
2013/02/14 00:10:37
Can we get rid of this?
Bob Nystrom
2013/02/14 01:14:19
That will be a bit more invasive, so I figured I'd
| |
| 29 var parts = [part1, part2, part3, part4, part5, part6, part7, part8] | 29 var parts = [part1, part2, part3, part4, part5, part6, part7, part8] |
| 30 .map((part) => part == null ? null : _getPath(part)).toList(); | 30 .map((part) => part == null ? null : _getPath(part)).toList(); |
| 31 | 31 |
| 32 return path.join(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], | 32 return path.join(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], |
| 33 parts[6], parts[7]); | 33 parts[6], parts[7]); |
| 34 } | 34 } |
| 35 | 35 |
| 36 /// Gets the basename, the file name without any leading directory path, for | |
| 37 /// [file], which can either be a [String], [File], or [Directory]. | |
| 38 String basename(file) => path.basename(_getPath(file)); | |
| 39 | |
| 40 /// Gets the the leading directory path for [file], which can either be a | |
| 41 /// [String], [File], or [Directory]. | |
| 42 String dirname(file) => path.dirname(_getPath(file)); | |
| 43 | |
| 44 /// Splits [entry] into its individual components. | 36 /// Splits [entry] into its individual components. |
| 45 List<String> splitPath(entry) => path.split(_getPath(entry)); | 37 List<String> splitPath(entry) => path.split(_getPath(entry)); |
|
nweiz
2013/02/14 00:10:37
Can we get rid of this?
Bob Nystrom
2013/02/14 01:14:19
Done.
| |
| 46 | 38 |
| 47 /// Returns whether or not [entry] is nested somewhere within [dir]. This just | 39 /// Returns whether or not [entry] is nested somewhere within [dir]. This just |
| 48 /// performs a path comparison; it doesn't look at the actual filesystem. | 40 /// performs a path comparison; it doesn't look at the actual filesystem. |
| 49 bool isBeneath(entry, dir) { | 41 bool isBeneath(entry, dir) { |
|
nweiz
2013/02/14 00:10:37
Since this just calls path methods now, we should
Bob Nystrom
2013/02/14 01:14:19
Done.
| |
| 50 var relative = relativeTo(entry, dir); | 42 var relative = path.relative(entry, from: dir); |
| 51 return !path.isAbsolute(relative) && splitPath(relative)[0] != '..'; | 43 return !path.isAbsolute(relative) && splitPath(relative)[0] != '..'; |
| 52 } | 44 } |
| 53 | 45 |
| 54 /// Returns the path to [target] from [base]. | |
| 55 String relativeTo(target, base) => path.relative(target, from: base); | |
| 56 | |
| 57 /// Determines if [path], which can be a [String] file path, a [File], or a | 46 /// Determines if [path], which can be a [String] file path, a [File], or a |
| 58 /// [Directory] exists on the file system. | 47 /// [Directory] exists on the file system. |
| 59 bool entryExists(path) => fileExists(path) || dirExists(path); | 48 bool entryExists(path) => fileExists(path) || dirExists(path); |
| 60 | 49 |
| 61 /// Determines if [file], which can be a [String] file path or a [File], exists | 50 /// Determines if [file], which can be a [String] file path or a [File], exists |
| 62 /// on the file system. | 51 /// on the file system. |
| 63 bool fileExists(file) => _getFile(file).existsSync(); | 52 bool fileExists(file) => _getFile(file).existsSync(); |
| 64 | 53 |
| 65 /// Reads the contents of the text file [file], which can either be a [String] | 54 /// Reads the contents of the text file [file], which can either be a [String] |
| 66 /// or a [File]. | 55 /// or a [File]. |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 86 | 75 |
| 87 // Sanity check: don't spew a huge file. | 76 // Sanity check: don't spew a huge file. |
| 88 log.io("Writing ${contents.length} characters to text file $path."); | 77 log.io("Writing ${contents.length} characters to text file $path."); |
| 89 if (!dontLogContents && contents.length < 1024 * 1024) { | 78 if (!dontLogContents && contents.length < 1024 * 1024) { |
| 90 log.fine("Contents:\n$contents"); | 79 log.fine("Contents:\n$contents"); |
| 91 } | 80 } |
| 92 | 81 |
| 93 return file..writeAsStringSync(contents); | 82 return file..writeAsStringSync(contents); |
| 94 } | 83 } |
| 95 | 84 |
| 96 /// Deletes [file], which can be a [String] or a [File]. Returns a [Future] | 85 /// Deletes [file], which can be a [String] or a [File]. |
| 97 /// that completes when the deletion is done. | |
| 98 File deleteFile(file) => _getFile(file)..delete(); | 86 File deleteFile(file) => _getFile(file)..delete(); |
| 99 | 87 |
| 100 /// Creates [file] (which can either be a [String] or a [File]), and writes | 88 /// Creates [file] (which can either be a [String] or a [File]), and writes |
| 101 /// [contents] to it. | 89 /// [contents] to it. |
| 102 File writeBinaryFile(file, List<int> contents) { | 90 File writeBinaryFile(file, List<int> contents) { |
| 103 var path = _getPath(file); | 91 var path = _getPath(file); |
| 104 file = new File(path); | 92 file = new File(path); |
| 105 | 93 |
| 106 log.io("Writing ${contents.length} bytes to binary file $path."); | 94 log.io("Writing ${contents.length} bytes to binary file $path."); |
| 107 file.openSync(FileMode.WRITE) | 95 file.openSync(FileMode.WRITE) |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 121 | 109 |
| 122 var file = new File(path); | 110 var file = new File(path); |
| 123 return stream.pipe(wrapOutputStream(file.openOutputStream())).then((_) { | 111 return stream.pipe(wrapOutputStream(file.openOutputStream())).then((_) { |
| 124 log.fine("Created $path from stream."); | 112 log.fine("Created $path from stream."); |
| 125 }); | 113 }); |
| 126 } | 114 } |
| 127 | 115 |
| 128 /// Creates a directory [dir]. | 116 /// Creates a directory [dir]. |
| 129 Directory createDir(dir) => _getDirectory(dir)..createSync(); | 117 Directory createDir(dir) => _getDirectory(dir)..createSync(); |
| 130 | 118 |
| 131 /// Ensures that [path] and all its parent directories exist. If they don't | 119 /// Ensures that [dirPath] and all its parent directories exist. If they don't |
| 132 /// exist, creates them. | 120 /// exist, creates them. |
| 133 Directory ensureDir(path) { | 121 Directory ensureDir(dirPath) { |
| 134 path = _getPath(path); | 122 dirPath = _getPath(dirPath); |
| 135 | 123 |
| 136 log.fine("Ensuring directory $path exists."); | 124 log.fine("Ensuring directory $dirPath exists."); |
| 137 var dir = new Directory(path); | 125 var dir = new Directory(dirPath); |
| 138 if (path == '.' || dirExists(path)) return dir; | 126 if (dirPath == '.' || dirExists(dirPath)) return dir; |
| 139 | 127 |
| 140 ensureDir(dirname(path)); | 128 ensureDir(path.dirname(dirPath)); |
| 141 | 129 |
| 142 try { | 130 try { |
| 143 createDir(dir); | 131 createDir(dir); |
| 144 } on DirectoryIOException catch (ex) { | 132 } on DirectoryIOException catch (ex) { |
| 145 // Error 17 means the directory already exists (or 183 on Windows). | 133 // Error 17 means the directory already exists (or 183 on Windows). |
| 146 if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) { | 134 if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) { |
| 147 log.fine("Got 'already exists' error when creating directory."); | 135 log.fine("Got 'already exists' error when creating directory."); |
| 148 } else { | 136 } else { |
| 149 throw ex; | 137 throw ex; |
| 150 } | 138 } |
| 151 } | 139 } |
| 152 | 140 |
| 153 return dir; | 141 return dir; |
| 154 } | 142 } |
| 155 | 143 |
| 156 /// Creates a temp directory whose name will be based on [dir] with a unique | 144 /// Creates a temp directory whose name will be based on [dir] with a unique |
| 157 /// suffix appended to it. If [dir] is not provided, a temp directory will be | 145 /// suffix appended to it. If [dir] is not provided, a temp directory will be |
| 158 /// created in a platform-dependent temporary location. Returns a [Future] that | 146 /// created in a platform-dependent temporary location. Returns the path of the |
| 159 /// completes when the directory is created. | 147 /// created directory. |
| 160 Directory createTempDir([dir = '']) { | 148 String createTempDir([dir = '']) { |
| 161 var tempDir = _getDirectory(dir).createTempSync(); | 149 var tempDir = _getDirectory(dir).createTempSync(); |
| 162 log.io("Created temp directory ${tempDir.path}"); | 150 log.io("Created temp directory ${tempDir.path}"); |
| 163 return tempDir; | 151 return tempDir.path; |
| 164 } | 152 } |
| 165 | 153 |
| 166 /// Asynchronously recursively deletes [dir], which can be a [String] or a | 154 /// Asynchronously recursively deletes [dir], which can be a [String] or a |
| 167 /// [Directory]. Returns a [Future] that completes when the deletion is done. | 155 /// [Directory]. Returns a [Future] that completes when the deletion is done. |
| 168 Future<Directory> deleteDir(dir) { | 156 Future<Directory> deleteDir(dir) { |
| 169 dir = _getDirectory(dir); | 157 dir = _getDirectory(dir); |
| 170 | 158 |
| 171 return _attemptRetryable(() => log.ioAsync("delete directory ${dir.path}", | 159 return _attemptRetryable(() => log.ioAsync("delete directory ${dir.path}", |
| 172 dir.delete(recursive: true))); | 160 dir.delete(recursive: true))); |
| 173 } | 161 } |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 211 var stackTrace; | 199 var stackTrace; |
| 212 try { | 200 try { |
| 213 throw ""; | 201 throw ""; |
| 214 } catch (_, localStackTrace) { | 202 } catch (_, localStackTrace) { |
| 215 stackTrace = localStackTrace; | 203 stackTrace = localStackTrace; |
| 216 } | 204 } |
| 217 | 205 |
| 218 var children = []; | 206 var children = []; |
| 219 lister.onError = (error) => completer.completeError(error, stackTrace); | 207 lister.onError = (error) => completer.completeError(error, stackTrace); |
| 220 lister.onDir = (file) { | 208 lister.onDir = (file) { |
| 221 if (!includeHiddenFiles && basename(file).startsWith('.')) return; | 209 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return; |
| 222 file = join(dir, basename(file)); | 210 file = join(dir, path.basename(file)); |
| 223 contents.add(file); | 211 contents.add(file); |
| 224 // TODO(nweiz): don't manually recurse once issue 7358 is fixed. Note that | 212 // TODO(nweiz): don't manually recurse once issue 7358 is fixed. Note that |
| 225 // once we remove the manual recursion, we'll need to explicitly filter | 213 // once we remove the manual recursion, we'll need to explicitly filter |
| 226 // out files in hidden directories. | 214 // out files in hidden directories. |
| 227 if (recursive) { | 215 if (recursive) { |
| 228 children.add(doList(new Directory(file), listedDirectories)); | 216 children.add(doList(new Directory(file), listedDirectories)); |
| 229 } | 217 } |
| 230 }; | 218 }; |
| 231 | 219 |
| 232 lister.onFile = (file) { | 220 lister.onFile = (file) { |
| 233 if (!includeHiddenFiles && basename(file).startsWith('.')) return; | 221 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return; |
| 234 contents.add(join(dir, basename(file))); | 222 contents.add(join(dir, path.basename(file))); |
| 235 }; | 223 }; |
| 236 | 224 |
| 237 return completer.future.then((contents) { | 225 return completer.future.then((contents) { |
| 238 return Future.wait(children).then((childContents) { | 226 return Future.wait(children).then((childContents) { |
| 239 contents.addAll(flatten(childContents)); | 227 contents.addAll(flatten(childContents)); |
| 240 return contents; | 228 return contents; |
| 241 }); | 229 }); |
| 242 }); | 230 }); |
| 243 } | 231 } |
| 244 | 232 |
| (...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 354 // code in bin or web. | 342 // code in bin or web. |
| 355 if (!isSelfLink) { | 343 if (!isSelfLink) { |
| 356 log.warning('Warning: Package "$name" does not have a "lib" directory so ' | 344 log.warning('Warning: Package "$name" does not have a "lib" directory so ' |
| 357 'you will not be able to import any libraries from it.'); | 345 'you will not be able to import any libraries from it.'); |
| 358 } | 346 } |
| 359 | 347 |
| 360 return _getFile(to); | 348 return _getFile(to); |
| 361 }); | 349 }); |
| 362 } | 350 } |
| 363 | 351 |
| 364 /// Given [entry] which may be a [String], [File], or [Directory] relative to | |
| 365 /// the current working directory, returns its full canonicalized path. | |
| 366 String getFullPath(entry) => path.absolute(_getPath(entry)); | |
| 367 | |
| 368 /// Returns whether or not [entry] is an absolute path. | |
| 369 bool isAbsolute(entry) => path.isAbsolute(_getPath(entry)); | |
| 370 | |
| 371 /// Resolves [target] relative to the location of pub.dart. | 352 /// Resolves [target] relative to the location of pub.dart. |
| 372 String relativeToPub(String target) { | 353 String relativeToPub(String target) { |
| 373 var scriptPath = new File(new Options().script).fullPathSync(); | 354 var scriptPath = new File(new Options().script).fullPathSync(); |
| 374 | 355 |
| 375 // Walk up until we hit the "util(s)" directory. This lets us figure out where | 356 // Walk up until we hit the "util(s)" directory. This lets us figure out where |
| 376 // we are if this function is called from pub.dart, or one of the tests, | 357 // we are if this function is called from pub.dart, or one of the tests, |
| 377 // which also live under "utils", or from the SDK where pub is in "util". | 358 // which also live under "utils", or from the SDK where pub is in "util". |
| 378 var utilDir = dirname(scriptPath); | 359 var utilDir = path.dirname(scriptPath); |
| 379 while (basename(utilDir) != 'utils' && basename(utilDir) != 'util') { | 360 while (path.basename(utilDir) != 'utils' && |
| 380 if (basename(utilDir) == '') throw 'Could not find path to pub.'; | 361 path.basename(utilDir) != 'util') { |
| 381 utilDir = dirname(utilDir); | 362 if (path.basename(utilDir) == '') throw 'Could not find path to pub.'; |
| 363 utilDir = path.dirname(utilDir); | |
| 382 } | 364 } |
| 383 | 365 |
| 384 return path.normalize(join(utilDir, 'pub', target)); | 366 return path.normalize(join(utilDir, 'pub', target)); |
| 385 } | 367 } |
| 386 | 368 |
| 387 // TODO(nweiz): add a ByteSink wrapper to make writing strings to stdout/stderr | 369 // TODO(nweiz): add a ByteSink wrapper to make writing strings to stdout/stderr |
| 388 // nicer. | 370 // nicer. |
| 389 | 371 |
| 390 /// A sink that writes to standard output. Errors piped to this stream will be | 372 /// A sink that writes to standard output. Errors piped to this stream will be |
| 391 /// surfaced to the top-level error handler. | 373 /// surfaced to the top-level error handler. |
| (...skipping 329 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 721 | 703 |
| 722 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] | 704 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] |
| 723 /// returned by [fn] completes, the temporary directory and all its contents | 705 /// returned by [fn] completes, the temporary directory and all its contents |
| 724 /// will be deleted. | 706 /// will be deleted. |
| 725 /// | 707 /// |
| 726 /// Returns a future that completes to the value that the future returned from | 708 /// Returns a future that completes to the value that the future returned from |
| 727 /// [fn] completes to. | 709 /// [fn] completes to. |
| 728 Future withTempDir(Future fn(String path)) { | 710 Future withTempDir(Future fn(String path)) { |
| 729 return defer(() { | 711 return defer(() { |
| 730 var tempDir = createTempDir(); | 712 var tempDir = createTempDir(); |
| 731 return fn(tempDir.path).whenComplete(() { | 713 return fn(tempDir).whenComplete(() { |
| 732 return deleteDir(tempDir); | 714 return deleteDir(tempDir); |
| 733 }); | 715 }); |
| 734 }); | 716 }); |
| 735 } | 717 } |
| 736 | 718 |
| 737 /// Extracts a `.tar.gz` file from [stream] to [destination], which can be a | 719 /// Extracts a `.tar.gz` file from [stream] to [destination], which can be a |
| 738 /// directory or a path. Returns whether or not the extraction was successful. | 720 /// directory or a path. Returns whether or not the extraction was successful. |
| 739 Future<bool> extractTarGz(Stream<List<int>> stream, destination) { | 721 Future<bool> extractTarGz(Stream<List<int>> stream, destination) { |
| 740 destination = _getPath(destination); | 722 destination = _getPath(destination); |
| 741 | 723 |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 828 var buffer = new StringBuffer(); | 810 var buffer = new StringBuffer(); |
| 829 buffer.add('Creating .tag.gz stream containing:\n'); | 811 buffer.add('Creating .tag.gz stream containing:\n'); |
| 830 contents.forEach((file) => buffer.add('$file\n')); | 812 contents.forEach((file) => buffer.add('$file\n')); |
| 831 log.fine(buffer.toString()); | 813 log.fine(buffer.toString()); |
| 832 | 814 |
| 833 // TODO(nweiz): Propagate errors to the returned stream (including non-zero | 815 // TODO(nweiz): Propagate errors to the returned stream (including non-zero |
| 834 // exit codes). See issue 3657. | 816 // exit codes). See issue 3657. |
| 835 var controller = new StreamController<List<int>>(); | 817 var controller = new StreamController<List<int>>(); |
| 836 | 818 |
| 837 if (baseDir == null) baseDir = path.current; | 819 if (baseDir == null) baseDir = path.current; |
| 838 baseDir = getFullPath(baseDir); | 820 baseDir = path.absolute(baseDir); |
| 839 contents = contents.map((entry) { | 821 contents = contents.map((entry) { |
| 840 entry = getFullPath(entry); | 822 entry = path.absolute(_getPath(entry)); |
| 841 if (!isBeneath(entry, baseDir)) { | 823 if (!isBeneath(entry, baseDir)) { |
| 842 throw 'Entry $entry is not inside $baseDir.'; | 824 throw 'Entry $entry is not inside $baseDir.'; |
| 843 } | 825 } |
| 844 return relativeTo(entry, baseDir); | 826 return path.relative(entry, from: baseDir); |
| 845 }).toList(); | 827 }).toList(); |
| 846 | 828 |
| 847 if (Platform.operatingSystem != "windows") { | 829 if (Platform.operatingSystem != "windows") { |
| 848 var args = ["--create", "--gzip", "--directory", baseDir]; | 830 var args = ["--create", "--gzip", "--directory", baseDir]; |
| 849 args.addAll(contents.map(_getPath)); | 831 args.addAll(contents.map(_getPath)); |
| 850 // TODO(nweiz): It's possible that enough command-line arguments will make | 832 // TODO(nweiz): It's possible that enough command-line arguments will make |
| 851 // the process choke, so at some point we should save the arguments to a | 833 // the process choke, so at some point we should save the arguments to a |
| 852 // file and pass them in via --files-from for tar and -i@filename for 7zip. | 834 // file and pass them in via --files-from for tar and -i@filename for 7zip. |
| 853 startProcess("tar", args).then((process) { | 835 startProcess("tar", args).then((process) { |
| 854 store(process.stdout, controller); | 836 store(process.stdout, controller); |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 941 Directory _getDirectory(entry) { | 923 Directory _getDirectory(entry) { |
| 942 if (entry is Directory) return entry; | 924 if (entry is Directory) return entry; |
| 943 return new Directory(entry); | 925 return new Directory(entry); |
| 944 } | 926 } |
| 945 | 927 |
| 946 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 928 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
| 947 Uri _getUri(uri) { | 929 Uri _getUri(uri) { |
| 948 if (uri is Uri) return uri; | 930 if (uri is Uri) return uri; |
| 949 return Uri.parse(uri); | 931 return Uri.parse(uri); |
| 950 } | 932 } |
| OLD | NEW |