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 |