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 /** | 5 /** |
6 * Helper functionality to make working with IO easier. | 6 * Helper functionality to make working with IO easier. |
7 */ | 7 */ |
8 library io; | 8 library io; |
9 | 9 |
10 import 'dart:io'; | 10 import 'dart:io'; |
11 import 'dart:isolate'; | 11 import 'dart:isolate'; |
12 import 'dart:uri'; | 12 import 'dart:uri'; |
13 | 13 |
14 // TODO(nweiz): Make this import better. | 14 // TODO(nweiz): Make this import better. |
15 import '../../pkg/http/lib/http.dart' as http; | 15 import '../../pkg/http/lib/http.dart' as http; |
16 import 'curl_client.dart'; | |
17 import 'log.dart' as log; | |
16 import 'utils.dart'; | 18 import 'utils.dart'; |
17 import 'curl_client.dart'; | |
18 | 19 |
19 bool _isGitInstalledCache; | 20 bool _isGitInstalledCache; |
20 | 21 |
21 /// The cached Git command. | 22 /// The cached Git command. |
22 String _gitCommandCache; | 23 String _gitCommandCache; |
23 | 24 |
24 /** Gets the current working directory. */ | 25 /** Gets the current working directory. */ |
25 String get currentWorkingDir => new File('.').fullPathSync(); | 26 String get currentWorkingDir => new File('.').fullPathSync(); |
26 | 27 |
27 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); | 28 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); |
28 | 29 |
29 /** | 30 /** |
30 * Prints the given string to `stderr` on its own line. | |
31 */ | |
32 void printError(value) { | |
33 stderr.writeString(value.toString()); | |
34 stderr.writeString('\n'); | |
35 } | |
36 | |
37 | |
38 /** | |
39 * Joins a number of path string parts into a single path. Handles | 31 * Joins a number of path string parts into a single path. Handles |
40 * platform-specific path separators. Parts can be [String], [Directory], or | 32 * platform-specific path separators. Parts can be [String], [Directory], or |
41 * [File] objects. | 33 * [File] objects. |
42 */ | 34 */ |
43 String join(part1, [part2, part3, part4]) { | 35 String join(part1, [part2, part3, part4]) { |
44 final parts = _sanitizePath(part1).split('/'); | 36 final parts = _sanitizePath(part1).split('/'); |
45 | 37 |
46 for (final part in [part2, part3, part4]) { | 38 for (final part in [part2, part3, part4]) { |
47 if (part == null) continue; | 39 if (part == null) continue; |
48 | 40 |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
115 return results[0] || results[1]; | 107 return results[0] || results[1]; |
116 }); | 108 }); |
117 } | 109 } |
118 | 110 |
119 /** | 111 /** |
120 * Asynchronously determines if [file], which can be a [String] file path or a | 112 * Asynchronously determines if [file], which can be a [String] file path or a |
121 * [File], exists on the file system. Returns a [Future] that completes with | 113 * [File], exists on the file system. Returns a [Future] that completes with |
122 * the result. | 114 * the result. |
123 */ | 115 */ |
124 Future<bool> fileExists(file) { | 116 Future<bool> fileExists(file) { |
125 return new File(_getPath(file)).exists(); | 117 var path = _getPath(file); |
118 return log.ioAsync("Seeing if file $path exists.", | |
119 new File(path).exists(), | |
120 (exists) => "File $path ${exists ? 'exists' : 'does not exist'}."); | |
126 } | 121 } |
127 | 122 |
128 /** | 123 /** |
129 * Reads the contents of the text file [file], which can either be a [String] or | 124 * Reads the contents of the text file [file], which can either be a [String] or |
130 * a [File]. | 125 * a [File]. |
131 */ | 126 */ |
132 Future<String> readTextFile(file) { | 127 Future<String> readTextFile(file) { |
133 return new File(_getPath(file)).readAsString(Encoding.UTF_8); | 128 var path = _getPath(file); |
129 log.io("Reading text file $path."); | |
130 return new File(path).readAsString(Encoding.UTF_8).transform((contents) { | |
nweiz
2012/12/05 23:56:54
Why aren't you using log.ioAsync here?
Bob Nystrom
2012/12/06 01:33:26
Because it uses log.fine and not log.io for the re
| |
131 // Sanity check: don't spew a huge file. | |
132 if (contents.length < 1024 * 1024) { | |
133 log.fine("Read $path. Contents:\n$contents"); | |
nweiz
2012/12/05 23:56:54
log.io? Seems like almost every log call in this f
Bob Nystrom
2012/12/06 01:33:26
My initial thought was that file contents and proc
nweiz
2012/12/06 19:40:02
I think
IO: Read file "foo" Contents:
| foo
| bar
Bob Nystrom
2012/12/07 21:13:44
Done.
| |
134 } else { | |
135 log.fine("Read ${contents.length} characters from $path."); | |
136 } | |
137 return contents; | |
138 }); | |
134 } | 139 } |
135 | 140 |
136 /** | 141 /** |
137 * Creates [file] (which can either be a [String] or a [File]), and writes | 142 * Creates [file] (which can either be a [String] or a [File]), and writes |
138 * [contents] to it. Completes when the file is written and closed. | 143 * [contents] to it. Completes when the file is written and closed. |
139 */ | 144 */ |
140 Future<File> writeTextFile(file, String contents) { | 145 Future<File> writeTextFile(file, String contents) { |
141 file = new File(_getPath(file)); | 146 var path = _getPath(file); |
147 file = new File(path); | |
148 | |
149 // Sanity check: don't spew a huge file. | |
150 log.io("Writing ${contents.length} characters to text file $path."); | |
151 if (contents.length < 1024 * 1024) { | |
152 log.fine("Contents:\n$contents"); | |
153 } | |
154 | |
142 return file.open(FileMode.WRITE).chain((opened) { | 155 return file.open(FileMode.WRITE).chain((opened) { |
143 return opened.writeString(contents).chain((ignore) { | 156 return opened.writeString(contents).chain((ignore) { |
144 return opened.close().transform((ignore) => file); | 157 return opened.close().transform((ignore) { |
nweiz
2012/12/05 23:56:54
"ignore" -> "_"
Bob Nystrom
2012/12/06 01:33:26
Done.
| |
158 log.fine("Wrote text file $path."); | |
159 return file; | |
160 }); | |
145 }); | 161 }); |
146 }); | 162 }); |
147 } | 163 } |
148 | 164 |
149 /** | 165 /** |
150 * Asynchronously deletes [file], which can be a [String] or a [File]. Returns a | 166 * Asynchronously deletes [file], which can be a [String] or a [File]. Returns a |
151 * [Future] that completes when the deletion is done. | 167 * [Future] that completes when the deletion is done. |
152 */ | 168 */ |
153 Future<File> deleteFile(file) { | 169 Future<File> deleteFile(file) { |
154 return new File(_getPath(file)).delete(); | 170 var path = _getPath(file); |
171 return log.ioAsync("delete file $path", | |
172 new File(path).delete()); | |
nweiz
2012/12/05 23:56:54
Looks like this could be one line? Same goes for s
Bob Nystrom
2012/12/06 01:33:26
I put the future stuff on the second line even whe
| |
155 } | 173 } |
156 | 174 |
157 /// Writes [stream] to a new file at [path], which may be a [String] or a | 175 /// Writes [stream] to a new file at [path], which may be a [String] or a |
158 /// [File]. Will replace any file already at that path. Completes when the file | 176 /// [File]. Will replace any file already at that path. Completes when the file |
159 /// is done being written. | 177 /// is done being written. |
160 Future<File> createFileFromStream(InputStream stream, path) { | 178 Future<File> createFileFromStream(InputStream stream, path) { |
161 path = _getPath(path); | 179 path = _getPath(path); |
162 | 180 |
181 log.io("Creating $path from stream."); | |
182 | |
163 var completer = new Completer<File>(); | 183 var completer = new Completer<File>(); |
164 var file = new File(path); | 184 var file = new File(path); |
165 var outputStream = file.openOutputStream(); | 185 var outputStream = file.openOutputStream(); |
166 stream.pipe(outputStream); | 186 stream.pipe(outputStream); |
167 | 187 |
168 outputStream.onClosed = () { | 188 outputStream.onClosed = () { |
189 log.fine("Created $path from stream."); | |
169 completer.complete(file); | 190 completer.complete(file); |
170 }; | 191 }; |
171 | 192 |
172 // TODO(nweiz): remove this when issue 4061 is fixed. | 193 // TODO(nweiz): remove this when issue 4061 is fixed. |
173 var stackTrace; | 194 var stackTrace; |
174 try { | 195 try { |
175 throw ""; | 196 throw ""; |
176 } catch (_, localStackTrace) { | 197 } catch (_, localStackTrace) { |
177 stackTrace = localStackTrace; | 198 stackTrace = localStackTrace; |
178 } | 199 } |
179 | 200 |
180 completeError(error) { | 201 completeError(error) { |
181 if (!completer.isComplete) completer.completeException(error, stackTrace); | 202 if (!completer.isComplete) completer.completeException(error, stackTrace); |
nweiz
2012/12/05 23:56:54
Log the error if the completer is complete.
Bob Nystrom
2012/12/06 01:33:26
Done.
| |
182 } | 203 } |
183 | 204 |
184 stream.onError = completeError; | 205 stream.onError = completeError; |
185 outputStream.onError = completeError; | 206 outputStream.onError = completeError; |
186 | 207 |
187 return completer.future; | 208 return completer.future; |
188 } | 209 } |
189 | 210 |
190 /** | 211 /** |
191 * Creates a directory [dir]. Returns a [Future] that completes when the | 212 * Creates a directory [dir]. Returns a [Future] that completes when the |
192 * directory is created. | 213 * directory is created. |
193 */ | 214 */ |
194 Future<Directory> createDir(dir) { | 215 Future<Directory> createDir(dir) { |
195 dir = _getDirectory(dir); | 216 dir = _getDirectory(dir); |
196 return dir.create(); | 217 return log.ioAsync("create directory ${dir.path}", |
218 dir.create()); | |
197 } | 219 } |
198 | 220 |
199 /** | 221 /** |
200 * Ensures that [path] and all its parent directories exist. If they don't | 222 * Ensures that [path] and all its parent directories exist. If they don't |
201 * exist, creates them. Returns a [Future] that completes once all the | 223 * exist, creates them. Returns a [Future] that completes once all the |
202 * directories are created. | 224 * directories are created. |
203 */ | 225 */ |
204 Future<Directory> ensureDir(path) { | 226 Future<Directory> ensureDir(path) { |
205 path = _getPath(path); | 227 path = _getPath(path); |
228 log.fine("Ensuring directory $path exists."); | |
206 if (path == '.') return new Future.immediate(new Directory('.')); | 229 if (path == '.') return new Future.immediate(new Directory('.')); |
207 | 230 |
208 return dirExists(path).chain((exists) { | 231 return dirExists(path).chain((exists) { |
209 if (exists) return new Future.immediate(new Directory(path)); | 232 if (exists) { |
233 log.fine("Directory $path already exists."); | |
234 return new Future.immediate(new Directory(path)); | |
235 } | |
236 | |
210 return ensureDir(dirname(path)).chain((_) { | 237 return ensureDir(dirname(path)).chain((_) { |
211 var completer = new Completer<Directory>(); | 238 var completer = new Completer<Directory>(); |
212 var future = createDir(path); | 239 var future = createDir(path); |
213 future.handleException((error) { | 240 future.handleException((error) { |
214 if (error is! DirectoryIOException) return false; | 241 if (error is! DirectoryIOException) return false; |
215 // Error 17 means the directory already exists (or 183 on Windows). | 242 // Error 17 means the directory already exists (or 183 on Windows). |
216 if (error.osError.errorCode != 17 && | 243 if (error.osError.errorCode != 17 && |
217 error.osError.errorCode != 183) return false; | 244 error.osError.errorCode != 183) return false; |
nweiz
2012/12/05 23:56:54
Log this case as well.
Bob Nystrom
2012/12/06 01:33:26
Done.
| |
218 | 245 |
219 completer.complete(_getDirectory(path)); | 246 completer.complete(_getDirectory(path)); |
220 return true; | 247 return true; |
221 }); | 248 }); |
222 future.then(completer.complete); | 249 future.then(completer.complete); |
223 return completer.future; | 250 return completer.future; |
224 }); | 251 }); |
225 }); | 252 }); |
226 } | 253 } |
227 | 254 |
228 /** | 255 /** |
229 * Creates a temp directory whose name will be based on [dir] with a unique | 256 * Creates a temp directory whose name will be based on [dir] with a unique |
230 * suffix appended to it. If [dir] is not provided, a temp directory will be | 257 * suffix appended to it. If [dir] is not provided, a temp directory will be |
231 * created in a platform-dependent temporary location. Returns a [Future] that | 258 * created in a platform-dependent temporary location. Returns a [Future] that |
232 * completes when the directory is created. | 259 * completes when the directory is created. |
233 */ | 260 */ |
234 Future<Directory> createTempDir([dir = '']) { | 261 Future<Directory> createTempDir([dir = '']) { |
235 dir = _getDirectory(dir); | 262 dir = _getDirectory(dir); |
236 return dir.createTemp(); | 263 return log.ioAsync("create temp directory ${dir.path}", |
264 dir.createTemp()); | |
237 } | 265 } |
238 | 266 |
239 /** | 267 /** |
240 * Asynchronously recursively deletes [dir], which can be a [String] or a | 268 * Asynchronously recursively deletes [dir], which can be a [String] or a |
241 * [Directory]. Returns a [Future] that completes when the deletion is done. | 269 * [Directory]. Returns a [Future] that completes when the deletion is done. |
242 */ | 270 */ |
243 Future<Directory> deleteDir(dir) { | 271 Future<Directory> deleteDir(dir) { |
244 dir = _getDirectory(dir); | 272 dir = _getDirectory(dir); |
245 return dir.delete(recursive: true); | 273 return log.ioAsync("delete directory ${dir.path}", |
274 dir.delete(recursive: true)); | |
246 } | 275 } |
247 | 276 |
248 /** | 277 /** |
249 * Asynchronously lists the contents of [dir], which can be a [String] directory | 278 * Asynchronously lists the contents of [dir], which can be a [String] directory |
250 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents | 279 * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents |
251 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files and | 280 * (defaults to `false`). If [includeHiddenFiles] is `true`, includes files and |
252 * directories beginning with `.` (defaults to `false`). | 281 * directories beginning with `.` (defaults to `false`). |
253 */ | 282 */ |
254 Future<List<String>> listDir(dir, | 283 Future<List<String>> listDir(dir, |
255 {bool recursive: false, bool includeHiddenFiles: false}) { | 284 {bool recursive: false, bool includeHiddenFiles: false}) { |
256 final completer = new Completer<List<String>>(); | 285 final completer = new Completer<List<String>>(); |
257 final contents = <String>[]; | 286 final contents = <String>[]; |
258 | 287 |
259 dir = _getDirectory(dir); | 288 dir = _getDirectory(dir); |
289 log.io("Listing directory ${dir.path}."); | |
260 var lister = dir.list(recursive: recursive); | 290 var lister = dir.list(recursive: recursive); |
261 | 291 |
262 lister.onDone = (done) { | 292 lister.onDone = (done) { |
263 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile | 293 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile |
264 // aren't guaranteed to be called in a certain order. So far, they seem to. | 294 // aren't guaranteed to be called in a certain order. So far, they seem to. |
265 if (done) completer.complete(contents); | 295 if (done) { |
296 log.fine("Listed directory ${dir.path}:\n" | |
297 "${Strings.join(contents, '\n')}"); | |
nweiz
2012/12/05 23:56:54
Indent contents.
Bob Nystrom
2012/12/06 01:33:26
See previous comment.
| |
298 completer.complete(contents); | |
299 } | |
266 }; | 300 }; |
267 | 301 |
268 // TODO(nweiz): remove this when issue 4061 is fixed. | 302 // TODO(nweiz): remove this when issue 4061 is fixed. |
269 var stackTrace; | 303 var stackTrace; |
270 try { | 304 try { |
271 throw ""; | 305 throw ""; |
272 } catch (_, localStackTrace) { | 306 } catch (_, localStackTrace) { |
273 stackTrace = localStackTrace; | 307 stackTrace = localStackTrace; |
274 } | 308 } |
275 | 309 |
(...skipping 10 matching lines...) Expand all Loading... | |
286 return completer.future; | 320 return completer.future; |
287 } | 321 } |
288 | 322 |
289 /** | 323 /** |
290 * Asynchronously determines if [dir], which can be a [String] directory path | 324 * Asynchronously determines if [dir], which can be a [String] directory path |
291 * or a [Directory], exists on the file system. Returns a [Future] that | 325 * or a [Directory], exists on the file system. Returns a [Future] that |
292 * completes with the result. | 326 * completes with the result. |
293 */ | 327 */ |
294 Future<bool> dirExists(dir) { | 328 Future<bool> dirExists(dir) { |
295 dir = _getDirectory(dir); | 329 dir = _getDirectory(dir); |
296 return dir.exists(); | 330 return log.ioAsync("Seeing if directory ${dir.path} exists.", |
331 dir.exists(), | |
332 (exists) => "Directory ${dir.path} " | |
333 "${exists ? 'exists' : 'does not exist'}."); | |
297 } | 334 } |
298 | 335 |
299 /** | 336 /** |
300 * "Cleans" [dir]. If that directory already exists, it will be deleted. Then a | 337 * "Cleans" [dir]. If that directory already exists, it will be deleted. Then a |
301 * new empty directory will be created. Returns a [Future] that completes when | 338 * new empty directory will be created. Returns a [Future] that completes when |
302 * the new clean directory is created. | 339 * the new clean directory is created. |
303 */ | 340 */ |
304 Future<Directory> cleanDir(dir) { | 341 Future<Directory> cleanDir(dir) { |
305 return dirExists(dir).chain((exists) { | 342 return dirExists(dir).chain((exists) { |
306 if (exists) { | 343 if (exists) { |
307 // Delete it first. | 344 // Delete it first. |
308 return deleteDir(dir).chain((_) => createDir(dir)); | 345 return deleteDir(dir).chain((_) => createDir(dir)); |
309 } else { | 346 } else { |
310 // Just create it. | 347 // Just create it. |
311 return createDir(dir); | 348 return createDir(dir); |
312 } | 349 } |
313 }); | 350 }); |
314 } | 351 } |
315 | 352 |
316 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with | 353 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with |
317 /// the destination directory. | 354 /// the destination directory. |
318 Future<Directory> renameDir(from, String to) { | 355 Future<Directory> renameDir(from, String to) { |
319 from = _getDirectory(from); | 356 from = _getDirectory(from); |
357 log.io("Renaming directory ${from.path} to $to."); | |
320 | 358 |
321 if (Platform.operatingSystem != 'windows') return from.rename(to); | 359 if (Platform.operatingSystem != 'windows') { |
360 return from.rename(to).transform((dir) { | |
361 log.fine("Renamed directory ${from.path} to $to."); | |
362 return dir; | |
363 }); | |
364 } | |
322 | 365 |
323 // On Windows, we sometimes get failures where the directory is still in use | 366 // On Windows, we sometimes get failures where the directory is still in use |
324 // when we try to move it. To be a bit more resilient, we wait and retry a | 367 // when we try to move it. To be a bit more resilient, we wait and retry a |
325 // few times. | 368 // few times. |
326 var attempts = 0; | 369 var attempts = 0; |
327 attemptRename(_) { | 370 attemptRename(_) { |
328 attempts++; | 371 attempts++; |
329 return from.rename(to).transformException((e) { | 372 return from.rename(to).transform((dir) { |
373 log.fine("Renamed directory ${from.path} to $to."); | |
374 return dir; | |
375 }).transformException((e) { | |
330 if (attempts >= 10) { | 376 if (attempts >= 10) { |
331 throw 'Could not move directory "${from.path}" to "$to". Gave up ' | 377 throw 'Could not move directory "${from.path}" to "$to". Gave up ' |
332 'after $attempts attempts.'; | 378 'after $attempts attempts.'; |
333 } | 379 } |
334 | 380 |
335 // Wait a bit and try again. | 381 // Wait a bit and try again. |
382 log.fine("Rename ${from.path} failed, retrying (attempt $attempts)."); | |
336 return sleep(500).chain(attemptRename); | 383 return sleep(500).chain(attemptRename); |
337 }); | 384 }); |
338 | 385 |
339 return from; | 386 return from; |
340 } | 387 } |
341 | 388 |
342 return attemptRename(null); | 389 return attemptRename(null); |
343 } | 390 } |
344 | 391 |
345 /** | 392 /** |
346 * Creates a new symlink that creates an alias from [from] to [to], both of | 393 * Creates a new symlink that creates an alias from [from] to [to], both of |
347 * which can be a [String], [File], or [Directory]. Returns a [Future] which | 394 * which can be a [String], [File], or [Directory]. Returns a [Future] which |
348 * completes to the symlink file (i.e. [to]). | 395 * completes to the symlink file (i.e. [to]). |
349 */ | 396 */ |
350 Future<File> createSymlink(from, to) { | 397 Future<File> createSymlink(from, to) { |
351 from = _getPath(from); | 398 from = _getPath(from); |
352 to = _getPath(to); | 399 to = _getPath(to); |
353 | 400 |
401 log.fine("Create symlink $from -> $to."); | |
402 | |
354 var command = 'ln'; | 403 var command = 'ln'; |
355 var args = ['-s', from, to]; | 404 var args = ['-s', from, to]; |
356 | 405 |
357 if (Platform.operatingSystem == 'windows') { | 406 if (Platform.operatingSystem == 'windows') { |
358 // Call mklink on Windows to create an NTFS junction point. Only works on | 407 // Call mklink on Windows to create an NTFS junction point. Only works on |
359 // Vista or later. (Junction points are available earlier, but the "mklink" | 408 // Vista or later. (Junction points are available earlier, but the "mklink" |
360 // command is not.) I'm using a junction point (/j) here instead of a soft | 409 // command is not.) I'm using a junction point (/j) here instead of a soft |
361 // link (/d) because the latter requires some privilege shenanigans that | 410 // link (/d) because the latter requires some privilege shenanigans that |
362 // I'm not sure how to specify from the command line. | 411 // I'm not sure how to specify from the command line. |
363 command = 'mklink'; | 412 command = 'mklink'; |
(...skipping 11 matching lines...) Expand all Loading... | |
375 * package [from] to [to], both of which can be a [String], [File], or | 424 * package [from] to [to], both of which can be a [String], [File], or |
376 * [Directory]. Returns a [Future] which completes to the symlink file (i.e. | 425 * [Directory]. Returns a [Future] which completes to the symlink file (i.e. |
377 * [to]). If [from] does not have a `lib` directory, this shows a warning if | 426 * [to]). If [from] does not have a `lib` directory, this shows a warning if |
378 * appropriate and then does nothing. | 427 * appropriate and then does nothing. |
379 */ | 428 */ |
380 Future<File> createPackageSymlink(String name, from, to, | 429 Future<File> createPackageSymlink(String name, from, to, |
381 {bool isSelfLink: false}) { | 430 {bool isSelfLink: false}) { |
382 // See if the package has a "lib" directory. | 431 // See if the package has a "lib" directory. |
383 from = join(from, 'lib'); | 432 from = join(from, 'lib'); |
384 return dirExists(from).chain((exists) { | 433 return dirExists(from).chain((exists) { |
434 log.fine("Creating package ${isSelfLink ? "self" : ""}link from " | |
435 "$from to $to."); | |
nweiz
2012/12/05 23:56:54
Including "$from" and "$to" seems redundant with c
Bob Nystrom
2012/12/06 01:33:26
Done.
| |
385 if (exists) return createSymlink(from, to); | 436 if (exists) return createSymlink(from, to); |
386 | 437 |
387 // It's OK for the self link (i.e. the root package) to not have a lib | 438 // It's OK for the self link (i.e. the root package) to not have a lib |
388 // directory since it may just be a leaf application that only has | 439 // directory since it may just be a leaf application that only has |
389 // code in bin or web. | 440 // code in bin or web. |
390 if (!isSelfLink) { | 441 if (!isSelfLink) { |
391 printError( | 442 log.warning('Package "$name" does not have a "lib" directory so you ' |
392 'Warning: Package "$name" does not have a "lib" directory so you ' | 443 'will not be able to import any libraries from it.'); |
393 'will not be able to import any libraries from it.'); | |
394 } | 444 } |
395 | 445 |
396 return new Future.immediate(to); | 446 return new Future.immediate(to); |
397 }); | 447 }); |
398 } | 448 } |
399 | 449 |
400 /// Given [entry] which may be a [String], [File], or [Directory] relative to | 450 /// Given [entry] which may be a [String], [File], or [Directory] relative to |
401 /// the current working directory, returns its full canonicalized path. | 451 /// the current working directory, returns its full canonicalized path. |
402 String getFullPath(entry) { | 452 String getFullPath(entry) { |
403 var path = _getPath(entry); | 453 var path = _getPath(entry); |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
499 | 549 |
500 /// An HTTP client that transforms 40* errors and socket exceptions into more | 550 /// An HTTP client that transforms 40* errors and socket exceptions into more |
501 /// user-friendly error messages. | 551 /// user-friendly error messages. |
502 class PubHttpClient extends http.BaseClient { | 552 class PubHttpClient extends http.BaseClient { |
503 final http.Client _inner; | 553 final http.Client _inner; |
504 | 554 |
505 PubHttpClient([http.Client inner]) | 555 PubHttpClient([http.Client inner]) |
506 : _inner = inner == null ? new http.Client() : inner; | 556 : _inner = inner == null ? new http.Client() : inner; |
507 | 557 |
508 Future<http.StreamedResponse> send(http.BaseRequest request) { | 558 Future<http.StreamedResponse> send(http.BaseRequest request) { |
559 log.io("Sending HTTP request $request."); | |
nweiz
2012/12/05 23:56:54
We should do the same trick as for reading/writing
Bob Nystrom
2012/12/06 01:33:26
Is that easy to do? I thought that would be tricky
nweiz
2012/12/06 19:40:02
For the response it may not be possible until we h
| |
560 | |
509 // TODO(nweiz): remove this when issue 4061 is fixed. | 561 // TODO(nweiz): remove this when issue 4061 is fixed. |
510 var stackTrace; | 562 var stackTrace; |
511 try { | 563 try { |
512 throw null; | 564 throw null; |
513 } catch (_, localStackTrace) { | 565 } catch (_, localStackTrace) { |
514 stackTrace = localStackTrace; | 566 stackTrace = localStackTrace; |
515 } | 567 } |
516 | 568 |
517 // TODO(nweiz): Ideally the timeout would extend to reading from the | 569 // TODO(nweiz): Ideally the timeout would extend to reading from the |
518 // response input stream, but until issue 3657 is fixed that's not feasible. | 570 // response input stream, but until issue 3657 is fixed that's not feasible. |
519 return timeout(_inner.send(request).chain((streamedResponse) { | 571 return timeout(_inner.send(request).chain((streamedResponse) { |
572 log.fine("Got response ${streamedResponse.statusCode} " | |
573 "${streamedResponse.reasonPhrase}."); | |
574 | |
520 if (streamedResponse.statusCode < 400) { | 575 if (streamedResponse.statusCode < 400) { |
521 return new Future.immediate(streamedResponse); | 576 return new Future.immediate(streamedResponse); |
522 } | 577 } |
523 | 578 |
524 return http.Response.fromStream(streamedResponse).transform((response) { | 579 return http.Response.fromStream(streamedResponse).transform((response) { |
525 throw new PubHttpException(response); | 580 throw new PubHttpException(response); |
526 }); | 581 }); |
527 }).transformException((e) { | 582 }).transformException((e) { |
528 if (e is SocketIOException && | 583 if (e is SocketIOException && |
529 e.osError != null && | 584 e.osError != null && |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
612 Future<PubProcessResult> runProcess(String executable, List<String> args, | 667 Future<PubProcessResult> runProcess(String executable, List<String> args, |
613 {workingDir, Map<String, String> environment}) { | 668 {workingDir, Map<String, String> environment}) { |
614 return _doProcess(Process.run, executable, args, workingDir, environment) | 669 return _doProcess(Process.run, executable, args, workingDir, environment) |
615 .transform((result) { | 670 .transform((result) { |
616 // TODO(rnystrom): Remove this and change to returning one string. | 671 // TODO(rnystrom): Remove this and change to returning one string. |
617 List<String> toLines(String output) { | 672 List<String> toLines(String output) { |
618 var lines = output.split(NEWLINE_PATTERN); | 673 var lines = output.split(NEWLINE_PATTERN); |
619 if (!lines.isEmpty && lines.last == "") lines.removeLast(); | 674 if (!lines.isEmpty && lines.last == "") lines.removeLast(); |
620 return lines; | 675 return lines; |
621 } | 676 } |
622 return new PubProcessResult(toLines(result.stdout), | 677 |
678 var pubResult = new PubProcessResult(toLines(result.stdout), | |
nweiz
2012/12/05 23:56:54
nit: "pubResult" -> "result"
Bob Nystrom
2012/12/06 01:33:26
"result" is the already the name of the closure's
| |
623 toLines(result.stderr), | 679 toLines(result.stderr), |
624 result.exitCode); | 680 result.exitCode); |
681 | |
682 log.processResult(executable, args, pubResult); | |
683 return pubResult; | |
625 }); | 684 }); |
626 } | 685 } |
627 | 686 |
628 /// Spawns the process located at [executable], passing in [args]. Returns a | 687 /// Spawns the process located at [executable], passing in [args]. Returns a |
629 /// [Future] that will complete with the [Process] once it's been started. | 688 /// [Future] that will complete with the [Process] once it's been started. |
630 /// | 689 /// |
631 /// The spawned process will inherit its parent's environment variables. If | 690 /// The spawned process will inherit its parent's environment variables. If |
632 /// [environment] is provided, that will be used to augment (not replace) the | 691 /// [environment] is provided, that will be used to augment (not replace) the |
633 /// the inherited variables. | 692 /// the inherited variables. |
634 Future<Process> startProcess(String executable, List<String> args, | 693 Future<Process> startProcess(String executable, List<String> args, |
635 {workingDir, Map<String, String> environment}) => | 694 {workingDir, Map<String, String> environment}) { |
636 _doProcess(Process.start, executable, args, workingDir, environment); | 695 return _doProcess(Process.start, executable, args, workingDir, environment); |
696 } | |
nweiz
2012/12/05 23:56:54
:(
Bob Nystrom
2012/12/06 01:33:26
Fixed. Leftover code from some discarded changes t
| |
637 | 697 |
638 /// Calls [fn] with appropriately modified arguments. [fn] should have the same | 698 /// Calls [fn] with appropriately modified arguments. [fn] should have the same |
639 /// signature as [Process.start], except that the returned [Future] may have a | 699 /// signature as [Process.start], except that the returned [Future] may have a |
640 /// type other than [Process]. | 700 /// type other than [Process]. |
641 Future _doProcess(Function fn, String executable, List<String> args, workingDir, | 701 Future _doProcess(Function fn, String executable, List<String> args, workingDir, |
642 Map<String, String> environment) { | 702 Map<String, String> environment) { |
643 // TODO(rnystrom): Should dart:io just handle this? | 703 // TODO(rnystrom): Should dart:io just handle this? |
644 // Spawning a process on Windows will not look for the executable in the | 704 // Spawning a process on Windows will not look for the executable in the |
645 // system path. So, if executable looks like it needs that (i.e. it doesn't | 705 // system path. So, if executable looks like it needs that (i.e. it doesn't |
646 // have any path separators in it), then spawn it through a shell. | 706 // have any path separators in it), then spawn it through a shell. |
647 if ((Platform.operatingSystem == "windows") && | 707 if ((Platform.operatingSystem == "windows") && |
648 (executable.indexOf('\\') == -1)) { | 708 (executable.indexOf('\\') == -1)) { |
649 args = flatten(["/c", executable, args]); | 709 args = flatten(["/c", executable, args]); |
650 executable = "cmd"; | 710 executable = "cmd"; |
651 } | 711 } |
652 | 712 |
653 final options = new ProcessOptions(); | 713 final options = new ProcessOptions(); |
654 if (workingDir != null) { | 714 if (workingDir != null) { |
655 options.workingDirectory = _getDirectory(workingDir).path; | 715 options.workingDirectory = _getDirectory(workingDir).path; |
656 } | 716 } |
657 | 717 |
658 if (environment != null) { | 718 if (environment != null) { |
659 options.environment = new Map.from(Platform.environment); | 719 options.environment = new Map.from(Platform.environment); |
660 environment.forEach((key, value) => options.environment[key] = value); | 720 environment.forEach((key, value) => options.environment[key] = value); |
661 } | 721 } |
662 | 722 |
723 log.process(executable, args); | |
724 | |
663 return fn(executable, args, options); | 725 return fn(executable, args, options); |
664 } | 726 } |
665 | 727 |
666 /// Closes [response] while ignoring the body of [request]. Returns a Future | 728 /// Closes [response] while ignoring the body of [request]. Returns a Future |
667 /// that completes once the response is closed. | 729 /// that completes once the response is closed. |
668 /// | 730 /// |
669 /// Due to issue 6984, it's necessary to drain the request body before closing | 731 /// Due to issue 6984, it's necessary to drain the request body before closing |
670 /// the response. | 732 /// the response. |
671 Future closeHttpResponse(HttpRequest request, HttpResponse response) { | 733 Future closeHttpResponse(HttpRequest request, HttpResponse response) { |
672 // TODO(nweiz): remove this when issue 4061 is fixed. | 734 // TODO(nweiz): remove this when issue 4061 is fixed. |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
715 if (completer.future.isComplete) return; | 777 if (completer.future.isComplete) return; |
716 timer.cancel(); | 778 timer.cancel(); |
717 completer.complete(value); | 779 completer.complete(value); |
718 }); | 780 }); |
719 return completer.future; | 781 return completer.future; |
720 } | 782 } |
721 | 783 |
722 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] | 784 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] |
723 /// returned by [fn] completes, the temporary directory and all its contents | 785 /// returned by [fn] completes, the temporary directory and all its contents |
724 /// will be deleted. | 786 /// will be deleted. |
725 Future withTempDir(Future fn(String path)) { | 787 Future withTempDir(Future fn(String path)) { |
nweiz
2012/12/05 23:56:54
It would probably help with debugging if this and
Bob Nystrom
2012/12/06 01:33:26
Agreed, but dart:io doesn't support that. :(
nweiz
2012/12/06 19:40:02
File a bug?
Bob Nystrom
2012/12/07 21:13:44
Eh, I don't care that much about it.
| |
726 var tempDir; | 788 var tempDir; |
727 var future = createTempDir().chain((dir) { | 789 var future = createTempDir().chain((dir) { |
728 tempDir = dir; | 790 tempDir = dir; |
791 log.fine('Within temp directory ${tempDir.path}.'); | |
nweiz
2012/12/05 23:56:54
"Within" is a little confusing, since the working
Bob Nystrom
2012/12/06 01:33:26
Removed.
| |
729 return fn(tempDir.path); | 792 return fn(tempDir.path); |
730 }); | 793 }); |
731 future.onComplete((_) => tempDir.delete(recursive: true)); | 794 future.onComplete((_) { |
795 log.fine('Cleaning up temp directory ${tempDir.path}.'); | |
796 deleteDir(tempDir); | |
797 }); | |
732 return future; | 798 return future; |
733 } | 799 } |
734 | 800 |
735 /// Tests whether or not the git command-line app is available for use. | 801 /// Tests whether or not the git command-line app is available for use. |
736 Future<bool> get isGitInstalled { | 802 Future<bool> get isGitInstalled { |
737 if (_isGitInstalledCache != null) { | 803 if (_isGitInstalledCache != null) { |
738 // TODO(rnystrom): The sleep is to pump the message queue. Can use | 804 // TODO(rnystrom): The sleep is to pump the message queue. Can use |
739 // Future.immediate() when #3356 is fixed. | 805 // Future.immediate() when #3356 is fixed. |
740 return sleep(0).transform((_) => _isGitInstalledCache); | 806 return sleep(0).transform((_) => _isGitInstalledCache); |
741 } | 807 } |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
794 return completer.future; | 860 return completer.future; |
795 } | 861 } |
796 | 862 |
797 /** | 863 /** |
798 * Extracts a `.tar.gz` file from [stream] to [destination], which can be a | 864 * Extracts a `.tar.gz` file from [stream] to [destination], which can be a |
799 * directory or a path. Returns whether or not the extraction was successful. | 865 * directory or a path. Returns whether or not the extraction was successful. |
800 */ | 866 */ |
801 Future<bool> extractTarGz(InputStream stream, destination) { | 867 Future<bool> extractTarGz(InputStream stream, destination) { |
802 destination = _getPath(destination); | 868 destination = _getPath(destination); |
803 | 869 |
870 log.fine("Extracting .tar.gz stream to $destination."); | |
871 | |
804 if (Platform.operatingSystem == "windows") { | 872 if (Platform.operatingSystem == "windows") { |
805 return _extractTarGzWindows(stream, destination); | 873 return _extractTarGzWindows(stream, destination); |
806 } | 874 } |
807 | 875 |
808 var completer = new Completer<int>(); | 876 var completer = new Completer<int>(); |
809 var processFuture = Process.start("tar", | 877 var processFuture = Process.start("tar", |
810 ["--extract", "--gunzip", "--directory", destination]); | 878 ["--extract", "--gunzip", "--directory", destination]); |
811 processFuture.then((process) { | 879 processFuture.then((process) { |
812 process.onExit = completer.complete; | 880 process.onExit = completer.complete; |
813 stream.pipe(process.stdin); | 881 stream.pipe(process.stdin); |
814 process.stdout.pipe(stdout, close: false); | 882 process.stdout.pipe(stdout, close: false); |
815 process.stderr.pipe(stderr, close: false); | 883 process.stderr.pipe(stderr, close: false); |
816 }); | 884 }); |
817 processFuture.handleException((error) { | 885 processFuture.handleException((error) { |
818 completer.completeException(error, processFuture.stackTrace); | 886 completer.completeException(error, processFuture.stackTrace); |
819 return true; | 887 return true; |
820 }); | 888 }); |
821 | 889 |
822 return completer.future.transform((exitCode) => exitCode == 0); | 890 return completer.future.transform((exitCode) { |
891 log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode."); | |
892 return exitCode == 0; | |
nweiz
2012/12/05 23:56:54
Not strictly related to this change, but do we eve
Bob Nystrom
2012/12/06 01:33:26
Not sure. I'll add a TODO.
| |
893 }); | |
823 } | 894 } |
824 | 895 |
825 Future<bool> _extractTarGzWindows(InputStream stream, String destination) { | 896 Future<bool> _extractTarGzWindows(InputStream stream, String destination) { |
826 // TODO(rnystrom): In the repo's history, there is an older implementation of | 897 // TODO(rnystrom): In the repo's history, there is an older implementation of |
827 // this that does everything in memory by piping streams directly together | 898 // this that does everything in memory by piping streams directly together |
828 // instead of writing out temp files. The code is simpler, but unfortunately, | 899 // instead of writing out temp files. The code is simpler, but unfortunately, |
829 // 7zip seems to periodically fail when we invoke it from Dart and tell it to | 900 // 7zip seems to periodically fail when we invoke it from Dart and tell it to |
830 // read from stdin instead of a file. Consider resurrecting that version if | 901 // read from stdin instead of a file. Consider resurrecting that version if |
831 // we can figure out why it fails. | 902 // we can figure out why it fails. |
832 | 903 |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
869 | 940 |
870 // Untar the archive into the destination directory. | 941 // Untar the archive into the destination directory. |
871 return runProcess(command, ['x', tarFile], workingDir: destination); | 942 return runProcess(command, ['x', tarFile], workingDir: destination); |
872 }).chain((result) { | 943 }).chain((result) { |
873 if (result.exitCode != 0) { | 944 if (result.exitCode != 0) { |
874 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n' | 945 throw 'Could not un-tar (exit code ${result.exitCode}). Error:\n' |
875 '${Strings.join(result.stdout, "\n")}\n' | 946 '${Strings.join(result.stdout, "\n")}\n' |
876 '${Strings.join(result.stderr, "\n")}'; | 947 '${Strings.join(result.stderr, "\n")}'; |
877 } | 948 } |
878 | 949 |
879 // Clean up the temp directory. | 950 log.fine('Clean up 7zip temp directory ${tempDir.path}.'); |
nweiz
2012/12/05 23:56:54
It seems more useful to log that tempDir is the 7z
Bob Nystrom
2012/12/06 01:33:26
You should see:
Extracting .tar.gz stream to $des
nweiz
2012/12/06 19:40:02
In that case, this log line is probably redundant
Bob Nystrom
2012/12/07 21:13:44
I don't think so. Since this is async, the delete
| |
880 // TODO(rnystrom): Should also delete this if anything fails. | 951 // TODO(rnystrom): Should also delete this if anything fails. |
881 return deleteDir(tempDir); | 952 return deleteDir(tempDir); |
882 }).transform((_) => true); | 953 }).transform((_) => true); |
883 } | 954 } |
884 | 955 |
885 /// Create a .tar.gz archive from a list of entries. Each entry can be a | 956 /// Create a .tar.gz archive from a list of entries. Each entry can be a |
886 /// [String], [Directory], or [File] object. The root of the archive is | 957 /// [String], [Directory], or [File] object. The root of the archive is |
887 /// considered to be [baseDir], which defaults to the current working directory. | 958 /// considered to be [baseDir], which defaults to the current working directory. |
888 /// Returns an [InputStream] that will emit the contents of the archive. | 959 /// Returns an [InputStream] that will emit the contents of the archive. |
889 InputStream createTarGz(List contents, {baseDir}) { | 960 InputStream createTarGz(List contents, {baseDir}) { |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
944 } | 1015 } |
945 | 1016 |
946 /** | 1017 /** |
947 * Exception thrown when an HTTP operation fails. | 1018 * Exception thrown when an HTTP operation fails. |
948 */ | 1019 */ |
949 class PubHttpException implements Exception { | 1020 class PubHttpException implements Exception { |
950 final http.Response response; | 1021 final http.Response response; |
951 | 1022 |
952 const PubHttpException(this.response); | 1023 const PubHttpException(this.response); |
953 | 1024 |
954 String toString() => 'HTTP error ${response.statusCode}: ${response.reason}'; | 1025 String toString() => 'HTTP error ${response.statusCode}: ' |
1026 '${response.reasonPhrase}'; | |
955 } | 1027 } |
956 | 1028 |
957 /** | 1029 /** |
958 * Exception thrown when an operation times out. | 1030 * Exception thrown when an operation times out. |
959 */ | 1031 */ |
960 class TimeoutException implements Exception { | 1032 class TimeoutException implements Exception { |
961 final String message; | 1033 final String message; |
962 | 1034 |
963 const TimeoutException(this.message); | 1035 const TimeoutException(this.message); |
964 | 1036 |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1015 return new Directory(entry); | 1087 return new Directory(entry); |
1016 } | 1088 } |
1017 | 1089 |
1018 /** | 1090 /** |
1019 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 1091 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
1020 */ | 1092 */ |
1021 Uri _getUri(uri) { | 1093 Uri _getUri(uri) { |
1022 if (uri is Uri) return uri; | 1094 if (uri is Uri) return uri; |
1023 return new Uri.fromString(uri); | 1095 return new Uri.fromString(uri); |
1024 } | 1096 } |
OLD | NEW |