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 |