| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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'; |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 118 /// Creates a temp directory whose name will be based on [dir] with a unique | 118 /// Creates a temp directory whose name will be based on [dir] with a unique |
| 119 /// suffix appended to it. If [dir] is not provided, a temp directory will be | 119 /// suffix appended to it. If [dir] is not provided, a temp directory will be |
| 120 /// created in a platform-dependent temporary location. Returns the path of the | 120 /// created in a platform-dependent temporary location. Returns the path of the |
| 121 /// created directory. | 121 /// created directory. |
| 122 String createTempDir([dir = '']) { | 122 String createTempDir([dir = '']) { |
| 123 var tempDir = new Directory(dir).createTempSync(); | 123 var tempDir = new Directory(dir).createTempSync(); |
| 124 log.io("Created temp directory ${tempDir.path}"); | 124 log.io("Created temp directory ${tempDir.path}"); |
| 125 return tempDir.path; | 125 return tempDir.path; |
| 126 } | 126 } |
| 127 | 127 |
| 128 /// Asynchronously lists the contents of [dir]. If [recursive] is `true`, lists | 128 // TODO(nweiz): rename includeHiddenFiles to includeHidden. |
| 129 /// subdirectory contents (defaults to `false`). If [includeHiddenFiles] is | 129 /// Lists the contents of [dir]. If [recursive] is `true`, lists subdirectory |
| 130 /// `true`, includes files and directories beginning with `.` (defaults to | 130 /// contents (defaults to `false`). If [includeHiddenFiles] is `true`, includes |
| 131 /// `false`). | 131 /// files and directories beginning with `.` (defaults to `false`). |
| 132 /// | 132 /// |
| 133 /// If [dir] is a string, the returned paths are guaranteed to begin with it. | 133 /// The returned paths are guaranteed to begin with [dir]. |
| 134 Future<List<String>> listDir(String dir, | 134 List<String> listDir(String dir, {bool recursive: false, |
| 135 {bool recursive: false, bool includeHiddenFiles: false}) { | 135 bool includeHiddenFiles: false}) { |
| 136 Future<List<String>> doList(String dir, Set<String> listedDirectories) { | 136 List<String> doList(String dir, Set<String> listedDirectories) { |
| 137 var contents = <String>[]; | 137 var contents = <String>[]; |
| 138 var completer = new Completer<List<String>>(); | |
| 139 | 138 |
| 140 // Avoid recursive symlinks. | 139 // Avoid recursive symlinks. |
| 141 var resolvedPath = new File(dir).fullPathSync(); | 140 var resolvedPath = new File(dir).fullPathSync(); |
| 142 if (listedDirectories.contains(resolvedPath)) { | 141 if (listedDirectories.contains(resolvedPath)) return []; |
| 143 return new Future.immediate([]); | |
| 144 } | |
| 145 | 142 |
| 146 listedDirectories = new Set<String>.from(listedDirectories); | 143 listedDirectories = new Set<String>.from(listedDirectories); |
| 147 listedDirectories.add(resolvedPath); | 144 listedDirectories.add(resolvedPath); |
| 148 | 145 |
| 149 log.io("Listing directory $dir."); | 146 log.io("Listing directory $dir."); |
| 150 var lister = new Directory(dir).list(); | |
| 151 | 147 |
| 152 var children = []; | 148 var children = []; |
| 153 lister.listen( | 149 for (var entity in new Directory(dir).listSync()) { |
| 154 (entity) { | 150 if (entity is File) { |
| 155 if (entity is File) { | 151 var file = entity.path; |
| 156 var file = entity.path; | 152 if (!includeHiddenFiles && path.basename(file).startsWith('.')) { |
| 157 if (!includeHiddenFiles && path.basename(file).startsWith('.')) { | 153 continue; |
| 158 return; | 154 } |
| 159 } | 155 contents.add(file); |
| 160 contents.add(file); | 156 } else if (entity is Directory) { |
| 161 } else if (entity is Directory) { | 157 var file = entity.path; |
| 162 var file = entity.path; | 158 if (!includeHiddenFiles && path.basename(file).startsWith('.')) { |
| 163 if (!includeHiddenFiles && path.basename(file).startsWith('.')) { | 159 continue; |
| 164 return; | 160 } |
| 165 } | 161 contents.add(file); |
| 166 contents.add(file); | 162 // TODO(nweiz): don't manually recurse once issue 4794 is fixed. |
| 167 // TODO(nweiz): don't manually recurse once issue 4794 is fixed. | 163 // Note that once we remove the manual recursion, we'll need to |
| 168 // Note that once we remove the manual recursion, we'll need to | 164 // explicitly filter out files in hidden directories. |
| 169 // explicitly filter out files in hidden directories. | 165 if (recursive) { |
| 170 if (recursive) { | 166 children.addAll(doList(file, listedDirectories)); |
| 171 children.add(doList(file, listedDirectories)); | 167 } |
| 172 } | 168 } |
| 173 } | 169 } |
| 174 }, | |
| 175 onDone: () { | |
| 176 // TODO(rnystrom): May need to sort here if it turns out | |
| 177 // onDir and onFile aren't guaranteed to be called in a | |
| 178 // certain order. So far, they seem to. | |
| 179 log.fine("Listed directory $dir:\n${contents.join('\n')}"); | |
| 180 completer.complete(contents); | |
| 181 }, | |
| 182 onError: (error) => completer.completeError(error)); | |
| 183 | 170 |
| 184 return completer.future.then((contents) { | 171 log.fine("Listed directory $dir:\n${contents.join('\n')}"); |
| 185 return Future.wait(children).then((childContents) { | 172 contents.addAll(children); |
| 186 contents.addAll(flatten(childContents)); | 173 return contents; |
| 187 return contents; | |
| 188 }); | |
| 189 }); | |
| 190 } | 174 } |
| 191 | 175 |
| 192 return doList(dir, new Set<String>()); | 176 return doList(dir, new Set<String>()); |
| 193 } | 177 } |
| 194 | 178 |
| 195 /// Returns whether [dir] exists on the file system. This will return `true` for | 179 /// Returns whether [dir] exists on the file system. This will return `true` for |
| 196 /// a symlink only if that symlink is unbroken and points to a directory. | 180 /// a symlink only if that symlink is unbroken and points to a directory. |
| 197 bool dirExists(String dir) => new Directory(dir).existsSync(); | 181 bool dirExists(String dir) => new Directory(dir).existsSync(); |
| 198 | 182 |
| 199 /// Deletes whatever's at [path], whether it's a file, directory, or symlink. If | 183 /// Deletes whatever's at [path], whether it's a file, directory, or symlink. If |
| 200 /// it's a directory, it will be deleted recursively. | 184 /// it's a directory, it will be deleted recursively. |
| 201 void deleteEntry(String path) { | 185 void deleteEntry(String path) { |
| 202 if (linkExists(path)) { | 186 if (linkExists(path)) { |
| 203 log.io("Deleting link $path."); | 187 log.io("Deleting link $path."); |
| 204 if (Platform.operatingSystem == 'windows') { | 188 if (Platform.operatingSystem == 'windows') { |
| 205 // TODO(nweiz): remove this when issue 9278 is fixed. | 189 // TODO(nweiz): remove this when issue 9278 is fixed. |
| 206 new Directory(path).deleteSync(); | 190 new Directory(path).deleteSync(); |
| 207 } else { | 191 } else { |
| 208 new Link(path).deleteSync(); | 192 new Link(path).deleteSync(); |
| 209 } | 193 } |
| 210 } else if (dirExists(path)) { | 194 } else if (dirExists(path)) { |
| 211 log.io("Deleting directory $path."); | 195 log.io("Deleting directory $path."); |
| 212 new Directory(path).deleteSync(recursive: true); | 196 new Directory(path).deleteSync(recursive: true); |
| 213 } else { | 197 } else if (fileExists(path)) { |
| 214 log.io("Deleting file $path."); | 198 log.io("Deleting file $path."); |
| 215 new File(path).deleteSync(); | 199 new File(path).deleteSync(); |
| 216 } | 200 } |
| 217 } | 201 } |
| 218 | 202 |
| 219 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a | 203 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a |
| 220 /// new empty directory will be created. | 204 /// new empty directory will be created. |
| 221 void cleanDir(String dir) { | 205 void cleanDir(String dir) { |
| 222 if (entryExists(dir)) deleteEntry(dir); | 206 if (entryExists(dir)) deleteEntry(dir); |
| 223 createDir(dir); | 207 createDir(dir); |
| 224 } | 208 } |
| 225 | 209 |
| 226 /// Renames (i.e. moves) the directory [from] to [to]. | 210 /// Renames (i.e. moves) the directory [from] to [to]. |
| 227 void renameDir(String from, String to) { | 211 void renameDir(String from, String to) { |
| 228 log.io("Renaming directory $from to $to."); | 212 log.io("Renaming directory $from to $to."); |
| 229 new Directory(from).renameSync(to); | 213 new Directory(from).renameSync(to); |
| 230 } | 214 } |
| 231 | 215 |
| 232 /// Creates a new symlink at path [symlink] that points to [target]. Returns a | 216 /// Creates a new symlink at path [symlink] that points to [target]. Returns a |
| 233 /// [Future] which completes to the path to the symlink file. | 217 /// [Future] which completes to the path to the symlink file. |
| 234 /// | 218 /// |
| 235 /// If [relative] is true, creates a symlink with a relative path from the | 219 /// If [relative] is true, creates a symlink with a relative path from the |
| 236 /// symlink to the target. Otherwise, uses the [target] path unmodified. | 220 /// symlink to the target. Otherwise, uses the [target] path unmodified. |
| 237 /// | 221 /// |
| 238 /// Note that on Windows, only directories may be symlinked to. | 222 /// Note that on Windows, only directories may be symlinked to. |
| 239 Future<String> createSymlink(String target, String symlink, | 223 void createSymlink(String target, String symlink, |
| 240 {bool relative: false}) { | 224 {bool relative: false}) { |
| 241 if (relative) { | 225 if (relative) { |
| 242 // Relative junction points are not supported on Windows. Instead, just | 226 // Relative junction points are not supported on Windows. Instead, just |
| 243 // make sure we have a clean absolute path because it will interpret a | 227 // make sure we have a clean absolute path because it will interpret a |
| 244 // relative path to be relative to the cwd, not the symlink, and will be | 228 // relative path to be relative to the cwd, not the symlink, and will be |
| 245 // confused by forward slashes. | 229 // confused by forward slashes. |
| 246 if (Platform.operatingSystem == 'windows') { | 230 if (Platform.operatingSystem == 'windows') { |
| 247 target = path.normalize(path.absolute(target)); | 231 target = path.normalize(path.absolute(target)); |
| 248 } else { | 232 } else { |
| 249 target = path.normalize( | 233 target = path.normalize( |
| 250 path.relative(target, from: path.dirname(symlink))); | 234 path.relative(target, from: path.dirname(symlink))); |
| 251 } | 235 } |
| 252 } | 236 } |
| 253 | 237 |
| 254 log.fine("Creating $symlink pointing to $target"); | 238 log.fine("Creating $symlink pointing to $target"); |
| 255 | 239 new Link(symlink).createSync(target); |
| 256 var command = 'ln'; | |
| 257 var args = ['-s', target, symlink]; | |
| 258 | |
| 259 if (Platform.operatingSystem == 'windows') { | |
| 260 // Call mklink on Windows to create an NTFS junction point. Only works on | |
| 261 // Vista or later. (Junction points are available earlier, but the "mklink" | |
| 262 // command is not.) I'm using a junction point (/j) here instead of a soft | |
| 263 // link (/d) because the latter requires some privilege shenanigans that | |
| 264 // I'm not sure how to specify from the command line. | |
| 265 command = 'mklink'; | |
| 266 args = ['/j', symlink, target]; | |
| 267 } | |
| 268 | |
| 269 // TODO(rnystrom): Check exit code and output? | |
| 270 return runProcess(command, args).then((result) => symlink); | |
| 271 } | 240 } |
| 272 | 241 |
| 273 /// Creates a new symlink that creates an alias at [symlink] that points to the | 242 /// Creates a new symlink that creates an alias at [symlink] that points to the |
| 274 /// `lib` directory of package [target]. Returns a [Future] which completes to | 243 /// `lib` directory of package [target]. If [target] does not have a `lib` |
| 275 /// the path to the symlink file. If [target] does not have a `lib` directory, | 244 /// directory, this shows a warning if appropriate and then does nothing. |
| 276 /// this shows a warning if appropriate and then does nothing. | |
| 277 /// | 245 /// |
| 278 /// If [relative] is true, creates a symlink with a relative path from the | 246 /// If [relative] is true, creates a symlink with a relative path from the |
| 279 /// symlink to the target. Otherwise, uses the [target] path unmodified. | 247 /// symlink to the target. Otherwise, uses the [target] path unmodified. |
| 280 Future<String> createPackageSymlink(String name, String target, String symlink, | 248 void createPackageSymlink(String name, String target, String symlink, |
| 281 {bool isSelfLink: false, bool relative: false}) { | 249 {bool isSelfLink: false, bool relative: false}) { |
| 282 return defer(() { | 250 // See if the package has a "lib" directory. |
| 283 // See if the package has a "lib" directory. | 251 target = path.join(target, 'lib'); |
| 284 target = path.join(target, 'lib'); | 252 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); |
| 285 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); | 253 if (dirExists(target)) { |
| 286 if (dirExists(target)) { | 254 createSymlink(target, symlink, relative: relative); |
| 287 return createSymlink(target, symlink, relative: relative); | 255 return; |
| 288 } | 256 } |
| 289 | 257 |
| 290 // It's OK for the self link (i.e. the root package) to not have a lib | 258 // It's OK for the self link (i.e. the root package) to not have a lib |
| 291 // directory since it may just be a leaf application that only has | 259 // directory since it may just be a leaf application that only has |
| 292 // code in bin or web. | 260 // code in bin or web. |
| 293 if (!isSelfLink) { | 261 if (!isSelfLink) { |
| 294 log.warning('Warning: Package "$name" does not have a "lib" directory so ' | 262 log.warning('Warning: Package "$name" does not have a "lib" directory so ' |
| 295 'you will not be able to import any libraries from it.'); | 263 'you will not be able to import any libraries from it.'); |
| 296 } | 264 } |
| 297 | |
| 298 return symlink; | |
| 299 }); | |
| 300 } | 265 } |
| 301 | 266 |
| 302 /// Resolves [target] relative to the location of pub.dart. | 267 /// Resolves [target] relative to the location of pub.dart. |
| 303 String relativeToPub(String target) { | 268 String relativeToPub(String target) { |
| 304 var scriptPath = new File(new Options().script).fullPathSync(); | 269 var scriptPath = new File(new Options().script).fullPathSync(); |
| 305 | 270 |
| 306 // Walk up until we hit the "util(s)" directory. This lets us figure out where | 271 // Walk up until we hit the "util(s)" directory. This lets us figure out where |
| 307 // we are if this function is called from pub.dart, or one of the tests, | 272 // we are if this function is called from pub.dart, or one of the tests, |
| 308 // which also live under "utils", or from the SDK where pub is in "util". | 273 // which also live under "utils", or from the SDK where pub is in "util". |
| 309 var utilDir = path.dirname(scriptPath); | 274 var utilDir = path.dirname(scriptPath); |
| (...skipping 334 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 644 // first we un-gzip it to a tar file. | 609 // first we un-gzip it to a tar file. |
| 645 // Note: Setting the working directory instead of passing in a full file | 610 // Note: Setting the working directory instead of passing in a full file |
| 646 // path because 7zip says "A full path is not allowed here." | 611 // path because 7zip says "A full path is not allowed here." |
| 647 return runProcess(command, ['e', 'data.tar.gz'], workingDir: tempDir); | 612 return runProcess(command, ['e', 'data.tar.gz'], workingDir: tempDir); |
| 648 }).then((result) { | 613 }).then((result) { |
| 649 if (result.exitCode != 0) { | 614 if (result.exitCode != 0) { |
| 650 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n' | 615 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n' |
| 651 '${result.stdout.join("\n")}\n' | 616 '${result.stdout.join("\n")}\n' |
| 652 '${result.stderr.join("\n")}'; | 617 '${result.stderr.join("\n")}'; |
| 653 } | 618 } |
| 619 |
| 654 // Find the tar file we just created since we don't know its name. | 620 // Find the tar file we just created since we don't know its name. |
| 655 return listDir(tempDir); | 621 var tarFile = listDir(tempDir).firstWhere( |
| 656 }).then((files) { | 622 (file) => path.extension(file) == '.tar', |
| 657 var tarFile; | 623 orElse: () { |
| 658 for (var file in files) { | 624 throw 'The gzip file did not contain a tar file.'; |
| 659 if (path.extension(file) == '.tar') { | 625 }); |
| 660 tarFile = file; | |
| 661 break; | |
| 662 } | |
| 663 } | |
| 664 | |
| 665 if (tarFile == null) throw 'The gzip file did not contain a tar file.'; | |
| 666 | 626 |
| 667 // Untar the archive into the destination directory. | 627 // Untar the archive into the destination directory. |
| 668 return runProcess(command, ['x', tarFile], workingDir: destination); | 628 return runProcess(command, ['x', tarFile], workingDir: destination); |
| 669 }).then((result) { | 629 }).then((result) { |
| 670 if (result.exitCode != 0) { | 630 if (result.exitCode != 0) { |
| 671 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n' | 631 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n' |
| 672 '${result.stdout.join("\n")}\n' | 632 '${result.stdout.join("\n")}\n' |
| 673 '${result.stderr.join("\n")}'; | 633 '${result.stderr.join("\n")}'; |
| 674 } | 634 } |
| 675 return true; | 635 return true; |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 771 const PubProcessResult(this.stdout, this.stderr, this.exitCode); | 731 const PubProcessResult(this.stdout, this.stderr, this.exitCode); |
| 772 | 732 |
| 773 bool get success => exitCode == 0; | 733 bool get success => exitCode == 0; |
| 774 } | 734 } |
| 775 | 735 |
| 776 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 736 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
| 777 Uri _getUri(uri) { | 737 Uri _getUri(uri) { |
| 778 if (uri is Uri) return uri; | 738 if (uri is Uri) return uri; |
| 779 return Uri.parse(uri); | 739 return Uri.parse(uri); |
| 780 } | 740 } |
| OLD | NEW |