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