| 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 | |
| 26 /// platform-specific path separators. Parts can be [String], [Directory], or | |
| 27 /// [File] objects. | |
| 28 String join(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(); | |
| 31 | |
| 32 return path.join(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], | |
| 33 parts[6], parts[7]); | |
| 34 } | |
| 35 | |
| 36 /// Returns whether or not [entry] is nested somewhere within [dir]. This just | 25 /// Returns whether or not [entry] is nested somewhere within [dir]. This just |
| 37 /// performs a path comparison; it doesn't look at the actual filesystem. | 26 /// performs a path comparison; it doesn't look at the actual filesystem. |
| 38 bool isBeneath(String entry, String dir) { | 27 bool isBeneath(String entry, String dir) { |
| 39 var relative = path.relative(entry, from: dir); | 28 var relative = path.relative(entry, from: dir); |
| 40 return !path.isAbsolute(relative) && path.split(relative)[0] != '..'; | 29 return !path.isAbsolute(relative) && path.split(relative)[0] != '..'; |
| 41 } | 30 } |
| 42 | 31 |
| 43 /// Determines if [path], which can be a [String] file path, a [File], or a | 32 /// Determines if a file or directory at [path] exists. |
| 44 /// [Directory] exists on the file system. | 33 bool entryExists(String path) => fileExists(path) || dirExists(path); |
| 45 bool entryExists(path) => fileExists(path) || dirExists(path); | |
| 46 | 34 |
| 47 /// Determines if [file], which can be a [String] file path or a [File], exists | 35 /// Determines if [file] exists on the file system. |
| 48 /// on the file system. | 36 bool fileExists(String file) => new File(file).existsSync(); |
| 49 bool fileExists(file) => _getFile(file).existsSync(); | |
| 50 | 37 |
| 51 /// Reads the contents of the text file [file], which can either be a [String] | 38 /// Reads the contents of the text file [file]. |
| 52 /// or a [File]. | 39 String readTextFile(String file) => |
| 53 String readTextFile(file) => _getFile(file).readAsStringSync(Encoding.UTF_8); | 40 new File(file).readAsStringSync(Encoding.UTF_8); |
| 54 | 41 |
| 55 /// Reads the contents of the binary file [file], which can either be a [String] | 42 /// Reads the contents of the binary file [file]. |
| 56 /// or a [File]. | 43 List<int> readBinaryFile(String file) { |
| 57 List<int> readBinaryFile(file) { | 44 log.io("Reading binary file $file."); |
| 58 var path = _getPath(file); | 45 var contents = new File(file).readAsBytesSync(); |
| 59 log.io("Reading binary file $path."); | 46 log.io("Read ${contents.length} bytes from $file."); |
| 60 var contents = new File(path).readAsBytesSync(); | |
| 61 log.io("Read ${contents.length} bytes from $path."); | |
| 62 return contents; | 47 return contents; |
| 63 } | 48 } |
| 64 | 49 |
| 65 /// Creates [file] (which can either be a [String] or a [File]), and writes | 50 /// Creates [file] and writes [contents] to it. |
| 66 /// [contents] to it. | |
| 67 /// | 51 /// |
| 68 /// If [dontLogContents] is true, the contents of the file will never be logged. | 52 /// If [dontLogContents] is true, the contents of the file will never be logged. |
| 69 File writeTextFile(file, String contents, {dontLogContents: false}) { | 53 String writeTextFile(String file, String contents, {dontLogContents: false}) { |
| 70 var path = _getPath(file); | |
| 71 file = new File(path); | |
| 72 | |
| 73 // Sanity check: don't spew a huge file. | 54 // Sanity check: don't spew a huge file. |
| 74 log.io("Writing ${contents.length} characters to text file $path."); | 55 log.io("Writing ${contents.length} characters to text file $file."); |
| 75 if (!dontLogContents && contents.length < 1024 * 1024) { | 56 if (!dontLogContents && contents.length < 1024 * 1024) { |
| 76 log.fine("Contents:\n$contents"); | 57 log.fine("Contents:\n$contents"); |
| 77 } | 58 } |
| 78 | 59 |
| 79 return file..writeAsStringSync(contents); | 60 new File(file).writeAsStringSync(contents); |
| 80 } | |
| 81 | |
| 82 /// Deletes [file], which can be a [String] or a [File]. | |
| 83 File deleteFile(file) => _getFile(file)..delete(); | |
| 84 | |
| 85 /// Creates [file] (which can either be a [String] or a [File]), and writes | |
| 86 /// [contents] to it. | |
| 87 File writeBinaryFile(file, List<int> contents) { | |
| 88 var path = _getPath(file); | |
| 89 file = new File(path); | |
| 90 | |
| 91 log.io("Writing ${contents.length} bytes to binary file $path."); | |
| 92 file.openSync(FileMode.WRITE) | |
| 93 ..writeListSync(contents, 0, contents.length) | |
| 94 ..closeSync(); | |
| 95 log.fine("Wrote text file $path."); | |
| 96 return file; | 61 return file; |
| 97 } | 62 } |
| 98 | 63 |
| 99 /// Writes [stream] to a new file at [path], which may be a [String] or a | 64 /// Deletes [file]. |
| 100 /// [File]. Will replace any file already at that path. Completes when the file | 65 void deleteFile(String file) { |
| 101 /// is done being written. | 66 new File(file).delete(); |
| 102 Future<File> createFileFromStream(Stream<List<int>> stream, path) { | 67 } |
| 103 path = _getPath(path); | |
| 104 | 68 |
| 105 log.io("Creating $path from stream."); | 69 /// Creates [file] and writes [contents] to it. |
| 70 String writeBinaryFile(String file, List<int> contents) { |
| 71 log.io("Writing ${contents.length} bytes to binary file $file."); |
| 72 new File(file).openSync(FileMode.WRITE) |
| 73 ..writeListSync(contents, 0, contents.length) |
| 74 ..closeSync(); |
| 75 log.fine("Wrote text file $file."); |
| 76 return file; |
| 77 } |
| 106 | 78 |
| 107 var file = new File(path); | 79 /// Writes [stream] to a new file at path [file]. Will replace any file already |
| 108 return stream.pipe(wrapOutputStream(file.openOutputStream())).then((_) { | 80 /// at that path. Completes when the file is done being written. |
| 109 log.fine("Created $path from stream."); | 81 Future<String> createFileFromStream(Stream<List<int>> stream, String file) { |
| 82 log.io("Creating $file from stream."); |
| 83 |
| 84 var stream = new File(file).openOutputStream(); |
| 85 return stream.pipe(wrapOutputStream(stream)).then((_) { |
| 86 log.fine("Created $file from stream."); |
| 87 return file; |
| 110 }); | 88 }); |
| 111 } | 89 } |
| 112 | 90 |
| 113 /// Creates a directory [dir]. | 91 /// Creates a directory [dir]. |
| 114 Directory createDir(dir) => _getDirectory(dir)..createSync(); | 92 String createDir(String dir) { |
| 93 new Directory(dir).createSync(); |
| 94 return dir; |
| 95 } |
| 115 | 96 |
| 116 /// Ensures that [dirPath] and all its parent directories exist. If they don't | 97 /// Ensures that [dirPath] and all its parent directories exist. If they don't |
| 117 /// exist, creates them. | 98 /// exist, creates them. |
| 118 Directory ensureDir(dirPath) { | 99 String ensureDir(String dirPath) { |
| 119 dirPath = _getPath(dirPath); | |
| 120 | |
| 121 log.fine("Ensuring directory $dirPath exists."); | 100 log.fine("Ensuring directory $dirPath exists."); |
| 122 var dir = new Directory(dirPath); | 101 var dir = new Directory(dirPath); |
| 123 if (dirPath == '.' || dirExists(dirPath)) return dir; | 102 if (dirPath == '.' || dirExists(dirPath)) return dirPath; |
| 124 | 103 |
| 125 ensureDir(path.dirname(dirPath)); | 104 ensureDir(path.dirname(dirPath)); |
| 126 | 105 |
| 127 try { | 106 try { |
| 128 createDir(dir); | 107 createDir(dirPath); |
| 129 } on DirectoryIOException catch (ex) { | 108 } on DirectoryIOException catch (ex) { |
| 130 // Error 17 means the directory already exists (or 183 on Windows). | 109 // Error 17 means the directory already exists (or 183 on Windows). |
| 131 if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) { | 110 if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) { |
| 132 log.fine("Got 'already exists' error when creating directory."); | 111 log.fine("Got 'already exists' error when creating directory."); |
| 133 } else { | 112 } else { |
| 134 throw ex; | 113 throw ex; |
| 135 } | 114 } |
| 136 } | 115 } |
| 137 | 116 |
| 138 return dir; | 117 return dirPath; |
| 139 } | 118 } |
| 140 | 119 |
| 141 /// Creates a temp directory whose name will be based on [dir] with a unique | 120 /// Creates a temp directory whose name will be based on [dir] with a unique |
| 142 /// suffix appended to it. If [dir] is not provided, a temp directory will be | 121 /// suffix appended to it. If [dir] is not provided, a temp directory will be |
| 143 /// created in a platform-dependent temporary location. Returns the path of the | 122 /// created in a platform-dependent temporary location. Returns the path of the |
| 144 /// created directory. | 123 /// created directory. |
| 145 String createTempDir([dir = '']) { | 124 String createTempDir([dir = '']) { |
| 146 var tempDir = _getDirectory(dir).createTempSync(); | 125 var tempDir = new Directory(dir).createTempSync(); |
| 147 log.io("Created temp directory ${tempDir.path}"); | 126 log.io("Created temp directory ${tempDir.path}"); |
| 148 return tempDir.path; | 127 return tempDir.path; |
| 149 } | 128 } |
| 150 | 129 |
| 151 /// Asynchronously recursively deletes [dir], which can be a [String] or a | 130 /// Asynchronously recursively deletes [dir]. Returns a [Future] that completes |
| 152 /// [Directory]. Returns a [Future] that completes when the deletion is done. | 131 /// when the deletion is done. |
| 153 Future<Directory> deleteDir(dir) { | 132 Future<String> deleteDir(String dir) { |
| 154 dir = _getDirectory(dir); | 133 return _attemptRetryable(() => log.ioAsync("delete directory $dir", |
| 155 | 134 new Directory(dir).delete(recursive: true).then((_) => dir))); |
| 156 return _attemptRetryable(() => log.ioAsync("delete directory ${dir.path}", | |
| 157 dir.delete(recursive: true))); | |
| 158 } | 135 } |
| 159 | 136 |
| 160 /// Asynchronously lists the contents of [dir], which can be a [String] | 137 /// Asynchronously lists the contents of [dir]. If [recursive] is `true`, lists |
| 161 /// directory path or a [Directory]. If [recursive] is `true`, lists | |
| 162 /// subdirectory contents (defaults to `false`). If [includeHiddenFiles] is | 138 /// subdirectory contents (defaults to `false`). If [includeHiddenFiles] is |
| 163 /// `true`, includes files and directories beginning with `.` (defaults to | 139 /// `true`, includes files and directories beginning with `.` (defaults to |
| 164 /// `false`). | 140 /// `false`). |
| 165 /// | 141 /// |
| 166 /// If [dir] is a string, the returned paths are guaranteed to begin with it. | 142 /// If [dir] is a string, the returned paths are guaranteed to begin with it. |
| 167 Future<List<String>> listDir(dir, | 143 Future<List<String>> listDir(String dir, |
| 168 {bool recursive: false, bool includeHiddenFiles: false}) { | 144 {bool recursive: false, bool includeHiddenFiles: false}) { |
| 169 Future<List<String>> doList(Directory dir, Set<String> listedDirectories) { | 145 Future<List<String>> doList(String dir, Set<String> listedDirectories) { |
| 170 var contents = <String>[]; | 146 var contents = <String>[]; |
| 171 var completer = new Completer<List<String>>(); | 147 var completer = new Completer<List<String>>(); |
| 172 | 148 |
| 173 // Avoid recursive symlinks. | 149 // Avoid recursive symlinks. |
| 174 var resolvedPath = new File(dir.path).fullPathSync(); | 150 var resolvedPath = new File(dir).fullPathSync(); |
| 175 if (listedDirectories.contains(resolvedPath)) { | 151 if (listedDirectories.contains(resolvedPath)) { |
| 176 return new Future.immediate([]); | 152 return new Future.immediate([]); |
| 177 } | 153 } |
| 178 | 154 |
| 179 listedDirectories = new Set<String>.from(listedDirectories); | 155 listedDirectories = new Set<String>.from(listedDirectories); |
| 180 listedDirectories.add(resolvedPath); | 156 listedDirectories.add(resolvedPath); |
| 181 | 157 |
| 182 log.io("Listing directory ${dir.path}."); | 158 log.io("Listing directory $dir."); |
| 183 var lister = dir.list(); | 159 var lister = new Directory(dir).list(); |
| 184 | 160 |
| 185 lister.onDone = (done) { | 161 lister.onDone = (done) { |
| 186 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile | 162 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile |
| 187 // aren't guaranteed to be called in a certain order. So far, they seem to
. | 163 // aren't guaranteed to be called in a certain order. So far, they seem to
. |
| 188 if (done) { | 164 if (done) { |
| 189 log.fine("Listed directory ${dir.path}:\n" | 165 log.fine("Listed directory $dir:\n${contents.join('\n')}"); |
| 190 "${contents.join('\n')}"); | |
| 191 completer.complete(contents); | 166 completer.complete(contents); |
| 192 } | 167 } |
| 193 }; | 168 }; |
| 194 | 169 |
| 195 // TODO(nweiz): remove this when issue 4061 is fixed. | 170 // TODO(nweiz): remove this when issue 4061 is fixed. |
| 196 var stackTrace; | 171 var stackTrace; |
| 197 try { | 172 try { |
| 198 throw ""; | 173 throw ""; |
| 199 } catch (_, localStackTrace) { | 174 } catch (_, localStackTrace) { |
| 200 stackTrace = localStackTrace; | 175 stackTrace = localStackTrace; |
| 201 } | 176 } |
| 202 | 177 |
| 203 var children = []; | 178 var children = []; |
| 204 lister.onError = (error) => completer.completeError(error, stackTrace); | 179 lister.onError = (error) => completer.completeError(error, stackTrace); |
| 205 lister.onDir = (file) { | 180 lister.onDir = (file) { |
| 206 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return; | 181 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return; |
| 207 file = join(dir, path.basename(file)); | 182 file = path.join(dir, path.basename(file)); |
| 208 contents.add(file); | 183 contents.add(file); |
| 209 // TODO(nweiz): don't manually recurse once issue 7358 is fixed. Note that | 184 // TODO(nweiz): don't manually recurse once issue 7358 is fixed. Note that |
| 210 // once we remove the manual recursion, we'll need to explicitly filter | 185 // once we remove the manual recursion, we'll need to explicitly filter |
| 211 // out files in hidden directories. | 186 // out files in hidden directories. |
| 212 if (recursive) { | 187 if (recursive) { |
| 213 children.add(doList(new Directory(file), listedDirectories)); | 188 children.add(doList(file, listedDirectories)); |
| 214 } | 189 } |
| 215 }; | 190 }; |
| 216 | 191 |
| 217 lister.onFile = (file) { | 192 lister.onFile = (file) { |
| 218 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return; | 193 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return; |
| 219 contents.add(join(dir, path.basename(file))); | 194 contents.add(path.join(dir, path.basename(file))); |
| 220 }; | 195 }; |
| 221 | 196 |
| 222 return completer.future.then((contents) { | 197 return completer.future.then((contents) { |
| 223 return Future.wait(children).then((childContents) { | 198 return Future.wait(children).then((childContents) { |
| 224 contents.addAll(flatten(childContents)); | 199 contents.addAll(flatten(childContents)); |
| 225 return contents; | 200 return contents; |
| 226 }); | 201 }); |
| 227 }); | 202 }); |
| 228 } | 203 } |
| 229 | 204 |
| 230 return doList(_getDirectory(dir), new Set<String>()); | 205 return doList(dir, new Set<String>()); |
| 231 } | 206 } |
| 232 | 207 |
| 233 /// Determines if [dir], which can be a [String] directory path or a | 208 /// Determines if [dir] exists on the file system. |
| 234 /// [Directory], exists on the file system. | 209 bool dirExists(String dir) => new Directory(dir).existsSync(); |
| 235 bool dirExists(dir) => _getDirectory(dir).existsSync(); | |
| 236 | 210 |
| 237 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a | 211 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a |
| 238 /// new empty directory will be created. Returns a [Future] that completes when | 212 /// new empty directory will be created. Returns a [Future] that completes when |
| 239 /// the new clean directory is created. | 213 /// the new clean directory is created. |
| 240 Future<Directory> cleanDir(dir) { | 214 Future<String> cleanDir(String dir) { |
| 241 return defer(() { | 215 return defer(() { |
| 242 if (dirExists(dir)) { | 216 if (dirExists(dir)) { |
| 243 // Delete it first. | 217 // Delete it first. |
| 244 return deleteDir(dir).then((_) => createDir(dir)); | 218 return deleteDir(dir).then((_) => createDir(dir)); |
| 245 } else { | 219 } else { |
| 246 // Just create it. | 220 // Just create it. |
| 247 return createDir(dir); | 221 return createDir(dir); |
| 248 } | 222 } |
| 249 }); | 223 }); |
| 250 } | 224 } |
| 251 | 225 |
| 252 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with | 226 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with |
| 253 /// the destination directory. | 227 /// the destination directory. |
| 254 Future<Directory> renameDir(from, String to) { | 228 Future<String> renameDir(String from, String to) { |
| 255 from = _getDirectory(from); | 229 log.io("Renaming directory $from to $to."); |
| 256 log.io("Renaming directory ${from.path} to $to."); | |
| 257 | 230 |
| 258 return _attemptRetryable(() => from.rename(to)).then((dir) { | 231 return _attemptRetryable(() => new Directory(from).rename(to)).then((dir) { |
| 259 log.fine("Renamed directory ${from.path} to $to."); | 232 log.fine("Renamed directory $from to $to."); |
| 260 return dir; | 233 return to; |
| 261 }); | 234 }); |
| 262 } | 235 } |
| 263 | 236 |
| 264 /// On Windows, we sometimes get failures where the directory is still in use | 237 /// On Windows, we sometimes get failures where the directory is still in use |
| 265 /// when we try to do something with it. This is usually because the OS hasn't | 238 /// when we try to do something with it. This is usually because the OS hasn't |
| 266 /// noticed yet that a process using that directory has closed. To be a bit | 239 /// noticed yet that a process using that directory has closed. To be a bit |
| 267 /// more resilient, we wait and retry a few times. | 240 /// more resilient, we wait and retry a few times. |
| 268 /// | 241 /// |
| 269 /// Takes a [callback] which returns a future for the operation being attempted. | 242 /// Takes a [callback] which returns a future for the operation being attempted. |
| 270 /// If that future completes with an error, it will slepp and then [callback] | 243 /// If that future completes with an error, it will slepp and then [callback] |
| (...skipping 13 matching lines...) Expand all Loading... |
| 284 | 257 |
| 285 // Wait a bit and try again. | 258 // Wait a bit and try again. |
| 286 log.fine("Operation failed, retrying (attempt $attempts)."); | 259 log.fine("Operation failed, retrying (attempt $attempts)."); |
| 287 return sleep(500).then(makeAttempt); | 260 return sleep(500).then(makeAttempt); |
| 288 }); | 261 }); |
| 289 } | 262 } |
| 290 | 263 |
| 291 return makeAttempt(null); | 264 return makeAttempt(null); |
| 292 } | 265 } |
| 293 | 266 |
| 294 /// Creates a new symlink that creates an alias from [from] to [to], both of | 267 /// Creates a new symlink that creates an alias from [from] to [to]. Returns a |
| 295 /// which can be a [String], [File], or [Directory]. Returns a [Future] which | 268 /// [Future] which completes to the symlink file (i.e. [to]). |
| 296 /// completes to the symlink file (i.e. [to]). | |
| 297 /// | 269 /// |
| 298 /// Note that on Windows, only directories may be symlinked to. | 270 /// Note that on Windows, only directories may be symlinked to. |
| 299 Future<File> createSymlink(from, to) { | 271 Future<String> createSymlink(String from, String to) { |
| 300 from = _getPath(from); | |
| 301 to = _getPath(to); | |
| 302 | |
| 303 log.fine("Creating symlink ($to is a symlink to $from)"); | 272 log.fine("Creating symlink ($to is a symlink to $from)"); |
| 304 | 273 |
| 305 var command = 'ln'; | 274 var command = 'ln'; |
| 306 var args = ['-s', from, to]; | 275 var args = ['-s', from, to]; |
| 307 | 276 |
| 308 if (Platform.operatingSystem == 'windows') { | 277 if (Platform.operatingSystem == 'windows') { |
| 309 // Call mklink on Windows to create an NTFS junction point. Only works on | 278 // Call mklink on Windows to create an NTFS junction point. Only works on |
| 310 // Vista or later. (Junction points are available earlier, but the "mklink" | 279 // Vista or later. (Junction points are available earlier, but the "mklink" |
| 311 // command is not.) I'm using a junction point (/j) here instead of a soft | 280 // command is not.) I'm using a junction point (/j) here instead of a soft |
| 312 // link (/d) because the latter requires some privilege shenanigans that | 281 // link (/d) because the latter requires some privilege shenanigans that |
| 313 // I'm not sure how to specify from the command line. | 282 // I'm not sure how to specify from the command line. |
| 314 command = 'mklink'; | 283 command = 'mklink'; |
| 315 args = ['/j', to, from]; | 284 args = ['/j', to, from]; |
| 316 } | 285 } |
| 317 | 286 |
| 318 return runProcess(command, args).then((result) { | 287 // TODO(rnystrom): Check exit code and output? |
| 319 // TODO(rnystrom): Check exit code and output? | 288 return runProcess(command, args).then((result) => to); |
| 320 return new File(to); | |
| 321 }); | |
| 322 } | 289 } |
| 323 | 290 |
| 324 /// Creates a new symlink that creates an alias from the `lib` directory of | 291 /// Creates a new symlink that creates an alias from the `lib` directory of |
| 325 /// package [from] to [to], both of which can be a [String], [File], or | 292 /// package [from] to [to]. Returns a [Future] which completes to the symlink |
| 326 /// [Directory]. Returns a [Future] which completes to the symlink file (i.e. | 293 /// file (i.e. [to]). If [from] does not have a `lib` directory, this shows a |
| 327 /// [to]). If [from] does not have a `lib` directory, this shows a warning if | 294 /// warning if appropriate and then does nothing. |
| 328 /// appropriate and then does nothing. | 295 Future<String> createPackageSymlink(String name, String from, String to, |
| 329 Future<File> createPackageSymlink(String name, from, to, | |
| 330 {bool isSelfLink: false}) { | 296 {bool isSelfLink: false}) { |
| 331 return defer(() { | 297 return defer(() { |
| 332 // See if the package has a "lib" directory. | 298 // See if the package has a "lib" directory. |
| 333 from = join(from, 'lib'); | 299 from = path.join(from, 'lib'); |
| 334 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); | 300 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); |
| 335 if (dirExists(from)) return createSymlink(from, to); | 301 if (dirExists(from)) return createSymlink(from, to); |
| 336 | 302 |
| 337 // It's OK for the self link (i.e. the root package) to not have a lib | 303 // It's OK for the self link (i.e. the root package) to not have a lib |
| 338 // directory since it may just be a leaf application that only has | 304 // directory since it may just be a leaf application that only has |
| 339 // code in bin or web. | 305 // code in bin or web. |
| 340 if (!isSelfLink) { | 306 if (!isSelfLink) { |
| 341 log.warning('Warning: Package "$name" does not have a "lib" directory so ' | 307 log.warning('Warning: Package "$name" does not have a "lib" directory so ' |
| 342 'you will not be able to import any libraries from it.'); | 308 'you will not be able to import any libraries from it.'); |
| 343 } | 309 } |
| 344 | 310 |
| 345 return _getFile(to); | 311 return to; |
| 346 }); | 312 }); |
| 347 } | 313 } |
| 348 | 314 |
| 349 /// Resolves [target] relative to the location of pub.dart. | 315 /// Resolves [target] relative to the location of pub.dart. |
| 350 String relativeToPub(String target) { | 316 String relativeToPub(String target) { |
| 351 var scriptPath = new File(new Options().script).fullPathSync(); | 317 var scriptPath = new File(new Options().script).fullPathSync(); |
| 352 | 318 |
| 353 // Walk up until we hit the "util(s)" directory. This lets us figure out where | 319 // Walk up until we hit the "util(s)" directory. This lets us figure out where |
| 354 // we are if this function is called from pub.dart, or one of the tests, | 320 // we are if this function is called from pub.dart, or one of the tests, |
| 355 // which also live under "utils", or from the SDK where pub is in "util". | 321 // which also live under "utils", or from the SDK where pub is in "util". |
| 356 var utilDir = path.dirname(scriptPath); | 322 var utilDir = path.dirname(scriptPath); |
| 357 while (path.basename(utilDir) != 'utils' && | 323 while (path.basename(utilDir) != 'utils' && |
| 358 path.basename(utilDir) != 'util') { | 324 path.basename(utilDir) != 'util') { |
| 359 if (path.basename(utilDir) == '') throw 'Could not find path to pub.'; | 325 if (path.basename(utilDir) == '') throw 'Could not find path to pub.'; |
| 360 utilDir = path.dirname(utilDir); | 326 utilDir = path.dirname(utilDir); |
| 361 } | 327 } |
| 362 | 328 |
| 363 return path.normalize(join(utilDir, 'pub', target)); | 329 return path.normalize(path.join(utilDir, 'pub', target)); |
| 364 } | 330 } |
| 365 | 331 |
| 366 // TODO(nweiz): add a ByteSink wrapper to make writing strings to stdout/stderr | 332 // TODO(nweiz): add a ByteSink wrapper to make writing strings to stdout/stderr |
| 367 // nicer. | 333 // nicer. |
| 368 | 334 |
| 369 /// A sink that writes to standard output. Errors piped to this stream will be | 335 /// A sink that writes to standard output. Errors piped to this stream will be |
| 370 /// surfaced to the top-level error handler. | 336 /// surfaced to the top-level error handler. |
| 371 final StreamSink<List<int>> stdoutSink = _wrapStdio(stdout, "stdout"); | 337 final StreamSink<List<int>> stdoutSink = _wrapStdio(stdout, "stdout"); |
| 372 | 338 |
| 373 /// A sink that writes to standard error. Errors piped to this stream will be | 339 /// A sink that writes to standard error. Errors piped to this stream will be |
| (...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 636 } | 602 } |
| 637 | 603 |
| 638 /// Sends [signal] to the underlying process. | 604 /// Sends [signal] to the underlying process. |
| 639 bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) => | 605 bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) => |
| 640 _process.kill(signal); | 606 _process.kill(signal); |
| 641 } | 607 } |
| 642 | 608 |
| 643 /// Calls [fn] with appropriately modified arguments. [fn] should have the same | 609 /// Calls [fn] with appropriately modified arguments. [fn] should have the same |
| 644 /// signature as [Process.start], except that the returned [Future] may have a | 610 /// signature as [Process.start], except that the returned [Future] may have a |
| 645 /// type other than [Process]. | 611 /// type other than [Process]. |
| 646 Future _doProcess(Function fn, String executable, List<String> args, workingDir, | 612 Future _doProcess(Function fn, String executable, List<String> args, |
| 647 Map<String, String> environment) { | 613 String workingDir, Map<String, String> environment) { |
| 648 // TODO(rnystrom): Should dart:io just handle this? | 614 // TODO(rnystrom): Should dart:io just handle this? |
| 649 // Spawning a process on Windows will not look for the executable in the | 615 // Spawning a process on Windows will not look for the executable in the |
| 650 // system path. So, if executable looks like it needs that (i.e. it doesn't | 616 // system path. So, if executable looks like it needs that (i.e. it doesn't |
| 651 // have any path separators in it), then spawn it through a shell. | 617 // have any path separators in it), then spawn it through a shell. |
| 652 if ((Platform.operatingSystem == "windows") && | 618 if ((Platform.operatingSystem == "windows") && |
| 653 (executable.indexOf('\\') == -1)) { | 619 (executable.indexOf('\\') == -1)) { |
| 654 args = flatten(["/c", executable, args]); | 620 args = flatten(["/c", executable, args]); |
| 655 executable = "cmd"; | 621 executable = "cmd"; |
| 656 } | 622 } |
| 657 | 623 |
| 658 final options = new ProcessOptions(); | 624 final options = new ProcessOptions(); |
| 659 if (workingDir != null) { | 625 if (workingDir != null) { |
| 660 options.workingDirectory = _getDirectory(workingDir).path; | 626 options.workingDirectory = workingDir; |
| 661 } | 627 } |
| 662 | 628 |
| 663 if (environment != null) { | 629 if (environment != null) { |
| 664 options.environment = new Map.from(Platform.environment); | 630 options.environment = new Map.from(Platform.environment); |
| 665 environment.forEach((key, value) => options.environment[key] = value); | 631 environment.forEach((key, value) => options.environment[key] = value); |
| 666 } | 632 } |
| 667 | 633 |
| 668 log.process(executable, args); | 634 log.process(executable, args); |
| 669 | 635 |
| 670 return fn(executable, args, options); | 636 return fn(executable, args, options); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 706 /// [fn] completes to. | 672 /// [fn] completes to. |
| 707 Future withTempDir(Future fn(String path)) { | 673 Future withTempDir(Future fn(String path)) { |
| 708 return defer(() { | 674 return defer(() { |
| 709 var tempDir = createTempDir(); | 675 var tempDir = createTempDir(); |
| 710 return fn(tempDir).whenComplete(() { | 676 return fn(tempDir).whenComplete(() { |
| 711 return deleteDir(tempDir); | 677 return deleteDir(tempDir); |
| 712 }); | 678 }); |
| 713 }); | 679 }); |
| 714 } | 680 } |
| 715 | 681 |
| 716 /// Extracts a `.tar.gz` file from [stream] to [destination], which can be a | 682 /// Extracts a `.tar.gz` file from [stream] to [destination]. Returns whether |
| 717 /// directory or a path. Returns whether or not the extraction was successful. | 683 /// or not the extraction was successful. |
| 718 Future<bool> extractTarGz(Stream<List<int>> stream, destination) { | 684 Future<bool> extractTarGz(Stream<List<int>> stream, String destination) { |
| 719 destination = _getPath(destination); | |
| 720 | |
| 721 log.fine("Extracting .tar.gz stream to $destination."); | 685 log.fine("Extracting .tar.gz stream to $destination."); |
| 722 | 686 |
| 723 if (Platform.operatingSystem == "windows") { | 687 if (Platform.operatingSystem == "windows") { |
| 724 return _extractTarGzWindows(stream, destination); | 688 return _extractTarGzWindows(stream, destination); |
| 725 } | 689 } |
| 726 | 690 |
| 727 return startProcess("tar", | 691 return startProcess("tar", |
| 728 ["--extract", "--gunzip", "--directory", destination]).then((process) { | 692 ["--extract", "--gunzip", "--directory", destination]).then((process) { |
| 729 // Ignore errors on process.std{out,err}. They'll be passed to | 693 // Ignore errors on process.std{out,err}. They'll be passed to |
| 730 // process.exitCode, and we don't want them being top-levelled by | 694 // process.exitCode, and we don't want them being top-levelled by |
| (...skipping 23 matching lines...) Expand all Loading... |
| 754 // read from stdin instead of a file. Consider resurrecting that version if | 718 // read from stdin instead of a file. Consider resurrecting that version if |
| 755 // we can figure out why it fails. | 719 // we can figure out why it fails. |
| 756 | 720 |
| 757 // Note: This line of code gets munged by create_sdk.py to be the correct | 721 // Note: This line of code gets munged by create_sdk.py to be the correct |
| 758 // relative path to 7zip in the SDK. | 722 // relative path to 7zip in the SDK. |
| 759 var pathTo7zip = '../../third_party/7zip/7za.exe'; | 723 var pathTo7zip = '../../third_party/7zip/7za.exe'; |
| 760 var command = relativeToPub(pathTo7zip); | 724 var command = relativeToPub(pathTo7zip); |
| 761 | 725 |
| 762 return withTempDir((tempDir) { | 726 return withTempDir((tempDir) { |
| 763 // Write the archive to a temp file. | 727 // Write the archive to a temp file. |
| 764 return createFileFromStream(stream, join(tempDir, 'data.tar.gz')).then((_) { | 728 var dataFile = path.join(tempDir, 'data.tar.gz'); |
| 729 return createFileFromStream(stream, dataFile).then((_) { |
| 765 // 7zip can't unarchive from gzip -> tar -> destination all in one step | 730 // 7zip can't unarchive from gzip -> tar -> destination all in one step |
| 766 // first we un-gzip it to a tar file. | 731 // first we un-gzip it to a tar file. |
| 767 // Note: Setting the working directory instead of passing in a full file | 732 // Note: Setting the working directory instead of passing in a full file |
| 768 // path because 7zip says "A full path is not allowed here." | 733 // path because 7zip says "A full path is not allowed here." |
| 769 return runProcess(command, ['e', 'data.tar.gz'], workingDir: tempDir); | 734 return runProcess(command, ['e', 'data.tar.gz'], workingDir: tempDir); |
| 770 }).then((result) { | 735 }).then((result) { |
| 771 if (result.exitCode != 0) { | 736 if (result.exitCode != 0) { |
| 772 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n' | 737 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n' |
| 773 '${result.stdout.join("\n")}\n' | 738 '${result.stdout.join("\n")}\n' |
| 774 '${result.stderr.join("\n")}'; | 739 '${result.stderr.join("\n")}'; |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 809 contents.forEach((file) => buffer.add('$file\n')); | 774 contents.forEach((file) => buffer.add('$file\n')); |
| 810 log.fine(buffer.toString()); | 775 log.fine(buffer.toString()); |
| 811 | 776 |
| 812 // TODO(nweiz): Propagate errors to the returned stream (including non-zero | 777 // TODO(nweiz): Propagate errors to the returned stream (including non-zero |
| 813 // exit codes). See issue 3657. | 778 // exit codes). See issue 3657. |
| 814 var controller = new StreamController<List<int>>(); | 779 var controller = new StreamController<List<int>>(); |
| 815 | 780 |
| 816 if (baseDir == null) baseDir = path.current; | 781 if (baseDir == null) baseDir = path.current; |
| 817 baseDir = path.absolute(baseDir); | 782 baseDir = path.absolute(baseDir); |
| 818 contents = contents.map((entry) { | 783 contents = contents.map((entry) { |
| 819 entry = path.absolute(_getPath(entry)); | 784 entry = path.absolute(entry); |
| 820 if (!isBeneath(entry, baseDir)) { | 785 if (!isBeneath(entry, baseDir)) { |
| 821 throw 'Entry $entry is not inside $baseDir.'; | 786 throw 'Entry $entry is not inside $baseDir.'; |
| 822 } | 787 } |
| 823 return path.relative(entry, from: baseDir); | 788 return path.relative(entry, from: baseDir); |
| 824 }).toList(); | 789 }).toList(); |
| 825 | 790 |
| 826 if (Platform.operatingSystem != "windows") { | 791 if (Platform.operatingSystem != "windows") { |
| 827 var args = ["--create", "--gzip", "--directory", baseDir]; | 792 var args = ["--create", "--gzip", "--directory", baseDir]; |
| 828 args.addAll(contents.map(_getPath)); | 793 args.addAll(contents); |
| 829 // TODO(nweiz): It's possible that enough command-line arguments will make | 794 // TODO(nweiz): It's possible that enough command-line arguments will make |
| 830 // the process choke, so at some point we should save the arguments to a | 795 // the process choke, so at some point we should save the arguments to a |
| 831 // file and pass them in via --files-from for tar and -i@filename for 7zip. | 796 // file and pass them in via --files-from for tar and -i@filename for 7zip. |
| 832 startProcess("tar", args).then((process) { | 797 startProcess("tar", args).then((process) { |
| 833 store(process.stdout, controller); | 798 store(process.stdout, controller); |
| 834 }).catchError((e) { | 799 }).catchError((e) { |
| 835 // We don't have to worry about double-signaling here, since the store() | 800 // We don't have to worry about double-signaling here, since the store() |
| 836 // above will only be reached if startProcess succeeds. | 801 // above will only be reached if startProcess succeeds. |
| 837 controller.signalError(e.error, e.stackTrace); | 802 controller.signalError(e.error, e.stackTrace); |
| 838 controller.close(); | 803 controller.close(); |
| 839 }); | 804 }); |
| 840 return new ByteStream(controller.stream); | 805 return new ByteStream(controller.stream); |
| 841 } | 806 } |
| 842 | 807 |
| 843 withTempDir((tempDir) { | 808 withTempDir((tempDir) { |
| 844 // Create the tar file. | 809 // Create the tar file. |
| 845 var tarFile = join(tempDir, "intermediate.tar"); | 810 var tarFile = path.join(tempDir, "intermediate.tar"); |
| 846 var args = ["a", "-w$baseDir", tarFile]; | 811 var args = ["a", "-w$baseDir", tarFile]; |
| 847 args.addAll(contents.map((entry) => '-i!"$entry"')); | 812 args.addAll(contents.map((entry) => '-i!"$entry"')); |
| 848 | 813 |
| 849 // Note: This line of code gets munged by create_sdk.py to be the correct | 814 // Note: This line of code gets munged by create_sdk.py to be the correct |
| 850 // relative path to 7zip in the SDK. | 815 // relative path to 7zip in the SDK. |
| 851 var pathTo7zip = '../../third_party/7zip/7za.exe'; | 816 var pathTo7zip = '../../third_party/7zip/7za.exe'; |
| 852 var command = relativeToPub(pathTo7zip); | 817 var command = relativeToPub(pathTo7zip); |
| 853 | 818 |
| 854 // We're passing 'baseDir' both as '-w' and setting it as the working | 819 // We're passing 'baseDir' both as '-w' and setting it as the working |
| 855 // directory explicitly here intentionally. The former ensures that the | 820 // directory explicitly here intentionally. The former ensures that the |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 890 class PubProcessResult { | 855 class PubProcessResult { |
| 891 final List<String> stdout; | 856 final List<String> stdout; |
| 892 final List<String> stderr; | 857 final List<String> stderr; |
| 893 final int exitCode; | 858 final int exitCode; |
| 894 | 859 |
| 895 const PubProcessResult(this.stdout, this.stderr, this.exitCode); | 860 const PubProcessResult(this.stdout, this.stderr, this.exitCode); |
| 896 | 861 |
| 897 bool get success => exitCode == 0; | 862 bool get success => exitCode == 0; |
| 898 } | 863 } |
| 899 | 864 |
| 900 /// Gets a dart:io [File] for [entry], which can either already be a File or be | |
| 901 /// a path string. | |
| 902 File _getFile(entry) { | |
| 903 if (entry is File) return entry; | |
| 904 if (entry is String) return new File(entry); | |
| 905 throw 'Entry $entry is not a supported type.'; | |
| 906 } | |
| 907 | |
| 908 /// Gets the path string for [entry], which can either already be a path string, | |
| 909 /// or be a [File] or [Directory]. Allows working generically with "file-like" | |
| 910 /// objects. | |
| 911 String _getPath(entry) { | |
| 912 if (entry is String) return entry; | |
| 913 if (entry is File) return entry.name; | |
| 914 if (entry is Directory) return entry.path; | |
| 915 throw 'Entry $entry is not a supported type.'; | |
| 916 } | |
| 917 | |
| 918 /// Gets a [Directory] for [entry], which can either already be one, or be a | |
| 919 /// [String]. | |
| 920 Directory _getDirectory(entry) { | |
| 921 if (entry is Directory) return entry; | |
| 922 return new Directory(entry); | |
| 923 } | |
| 924 | |
| 925 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 865 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
| 926 Uri _getUri(uri) { | 866 Uri _getUri(uri) { |
| 927 if (uri is Uri) return uri; | 867 if (uri is Uri) return uri; |
| 928 return Uri.parse(uri); | 868 return Uri.parse(uri); |
| 929 } | 869 } |
| OLD | NEW |