Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(213)

Side by Side Diff: utils/pub/io.dart

Issue 14297021: Move pub into sdk/lib/_internal. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Disallow package: imports of pub. Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « utils/pub/http.dart ('k') | utils/pub/lock_file.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 /// Helper functionality to make working with IO easier.
6 library io;
7
8 import 'dart:async';
9 import 'dart:io';
10 import 'dart:isolate';
11 import 'dart:json';
12 import 'dart:uri';
13
14 import 'package:pathos/path.dart' as path;
15 import 'package:http/http.dart' show ByteStream;
16 import 'error_group.dart';
17 import 'exit_codes.dart' as exit_codes;
18 import 'log.dart' as log;
19 import 'utils.dart';
20
21 export 'package:http/http.dart' show ByteStream;
22
23 /// Returns whether or not [entry] is nested somewhere within [dir]. This just
24 /// performs a path comparison; it doesn't look at the actual filesystem.
25 bool isBeneath(String entry, String dir) {
26 var relative = path.relative(entry, from: dir);
27 return !path.isAbsolute(relative) && path.split(relative)[0] != '..';
28 }
29
30 /// Determines if a file or directory exists at [path].
31 bool entryExists(String path) =>
32 dirExists(path) || fileExists(path) || linkExists(path);
33
34 /// Returns whether [link] exists on the file system. This will return `true`
35 /// for any symlink, regardless of what it points at or whether it's broken.
36 bool linkExists(String link) => new Link(link).existsSync();
37
38 /// Returns whether [file] exists on the file system. This will return `true`
39 /// for a symlink only if that symlink is unbroken and points to a file.
40 bool fileExists(String file) => new File(file).existsSync();
41
42 /// Reads the contents of the text file [file].
43 String readTextFile(String file) =>
44 new File(file).readAsStringSync(encoding: Encoding.UTF_8);
45
46 /// Reads the contents of the binary file [file].
47 List<int> readBinaryFile(String file) {
48 log.io("Reading binary file $file.");
49 var contents = new File(file).readAsBytesSync();
50 log.io("Read ${contents.length} bytes from $file.");
51 return contents;
52 }
53
54 /// Creates [file] and writes [contents] to it.
55 ///
56 /// If [dontLogContents] is true, the contents of the file will never be logged.
57 String writeTextFile(String file, String contents, {dontLogContents: false}) {
58 // Sanity check: don't spew a huge file.
59 log.io("Writing ${contents.length} characters to text file $file.");
60 if (!dontLogContents && contents.length < 1024 * 1024) {
61 log.fine("Contents:\n$contents");
62 }
63
64 new File(file).writeAsStringSync(contents);
65 return file;
66 }
67
68 /// Creates [file] and writes [contents] to it.
69 String writeBinaryFile(String file, List<int> contents) {
70 log.io("Writing ${contents.length} bytes to binary file $file.");
71 new File(file).openSync(mode: FileMode.WRITE)
72 ..writeFromSync(contents)
73 ..closeSync();
74 log.fine("Wrote text file $file.");
75 return file;
76 }
77
78 /// Writes [stream] to a new file at path [file]. Will replace any file already
79 /// at that path. Completes when the file is done being written.
80 Future<String> createFileFromStream(Stream<List<int>> stream, String file) {
81 log.io("Creating $file from stream.");
82
83 return stream.pipe(new File(file).openWrite()).then((_) {
84 log.fine("Created $file from stream.");
85 return file;
86 });
87 }
88
89 /// Creates a directory [dir].
90 String createDir(String dir) {
91 new Directory(dir).createSync();
92 return dir;
93 }
94
95 /// Ensures that [dirPath] and all its parent directories exist. If they don't
96 /// exist, creates them.
97 String ensureDir(String dirPath) {
98 log.fine("Ensuring directory $dirPath exists.");
99 var dir = new Directory(dirPath);
100 if (dirPath == '.' || dirExists(dirPath)) return dirPath;
101
102 ensureDir(path.dirname(dirPath));
103
104 try {
105 createDir(dirPath);
106 } on DirectoryIOException catch (ex) {
107 // Error 17 means the directory already exists (or 183 on Windows).
108 if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) {
109 log.fine("Got 'already exists' error when creating directory.");
110 } else {
111 throw ex;
112 }
113 }
114
115 return dirPath;
116 }
117
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
120 /// created in a platform-dependent temporary location. Returns the path of the
121 /// created directory.
122 String createTempDir([dir = '']) {
123 var tempDir = new Directory(dir).createTempSync();
124 log.io("Created temp directory ${tempDir.path}");
125 return tempDir.path;
126 }
127
128 /// Lists the contents of [dir]. If [recursive] is `true`, lists subdirectory
129 /// contents (defaults to `false`). If [includeHidden] is `true`, includes files
130 /// and directories beginning with `.` (defaults to `false`).
131 ///
132 /// The returned paths are guaranteed to begin with [dir].
133 List<String> listDir(String dir, {bool recursive: false,
134 bool includeHidden: false}) {
135 List<String> doList(String dir, Set<String> listedDirectories) {
136 var contents = <String>[];
137
138 // Avoid recursive symlinks.
139 var resolvedPath = new File(dir).fullPathSync();
140 if (listedDirectories.contains(resolvedPath)) return [];
141
142 listedDirectories = new Set<String>.from(listedDirectories);
143 listedDirectories.add(resolvedPath);
144
145 log.io("Listing directory $dir.");
146
147 var children = <String>[];
148 for (var entity in new Directory(dir).listSync()) {
149 if (!includeHidden && path.basename(entity.path).startsWith('.')) {
150 continue;
151 }
152
153 contents.add(entity.path);
154 if (entity is Directory) {
155 // TODO(nweiz): don't manually recurse once issue 4794 is fixed.
156 // Note that once we remove the manual recursion, we'll need to
157 // explicitly filter out files in hidden directories.
158 if (recursive) {
159 children.addAll(doList(entity.path, listedDirectories));
160 }
161 }
162 }
163
164 log.fine("Listed directory $dir:\n${contents.join('\n')}");
165 contents.addAll(children);
166 return contents;
167 }
168
169 return doList(dir, new Set<String>());
170 }
171
172 /// Returns whether [dir] exists on the file system. This will return `true` for
173 /// a symlink only if that symlink is unbroken and points to a directory.
174 bool dirExists(String dir) => new Directory(dir).existsSync();
175
176 /// Deletes whatever's at [path], whether it's a file, directory, or symlink. If
177 /// it's a directory, it will be deleted recursively.
178 void deleteEntry(String path) {
179 if (linkExists(path)) {
180 log.io("Deleting link $path.");
181 new Link(path).deleteSync();
182 } else if (dirExists(path)) {
183 log.io("Deleting directory $path.");
184 new Directory(path).deleteSync(recursive: true);
185 } else if (fileExists(path)) {
186 log.io("Deleting file $path.");
187 new File(path).deleteSync();
188 }
189 }
190
191 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a
192 /// new empty directory will be created.
193 void cleanDir(String dir) {
194 if (entryExists(dir)) deleteEntry(dir);
195 createDir(dir);
196 }
197
198 /// Renames (i.e. moves) the directory [from] to [to].
199 void renameDir(String from, String to) {
200 log.io("Renaming directory $from to $to.");
201 new Directory(from).renameSync(to);
202 }
203
204 /// Creates a new symlink at path [symlink] that points to [target]. Returns a
205 /// [Future] which completes to the path to the symlink file.
206 ///
207 /// If [relative] is true, creates a symlink with a relative path from the
208 /// symlink to the target. Otherwise, uses the [target] path unmodified.
209 ///
210 /// Note that on Windows, only directories may be symlinked to.
211 void createSymlink(String target, String symlink,
212 {bool relative: false}) {
213 if (relative) {
214 // Relative junction points are not supported on Windows. Instead, just
215 // make sure we have a clean absolute path because it will interpret a
216 // relative path to be relative to the cwd, not the symlink, and will be
217 // confused by forward slashes.
218 if (Platform.operatingSystem == 'windows') {
219 target = path.normalize(path.absolute(target));
220 } else {
221 target = path.normalize(
222 path.relative(target, from: path.dirname(symlink)));
223 }
224 }
225
226 log.fine("Creating $symlink pointing to $target");
227 new Link(symlink).createSync(target);
228 }
229
230 /// Creates a new symlink that creates an alias at [symlink] that points to the
231 /// `lib` directory of package [target]. If [target] does not have a `lib`
232 /// directory, this shows a warning if appropriate and then does nothing.
233 ///
234 /// If [relative] is true, creates a symlink with a relative path from the
235 /// symlink to the target. Otherwise, uses the [target] path unmodified.
236 void createPackageSymlink(String name, String target, String symlink,
237 {bool isSelfLink: false, bool relative: false}) {
238 // See if the package has a "lib" directory.
239 target = path.join(target, 'lib');
240 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'.");
241 if (dirExists(target)) {
242 createSymlink(target, symlink, relative: relative);
243 return;
244 }
245
246 // It's OK for the self link (i.e. the root package) to not have a lib
247 // directory since it may just be a leaf application that only has
248 // code in bin or web.
249 if (!isSelfLink) {
250 log.warning('Warning: Package "$name" does not have a "lib" directory so '
251 'you will not be able to import any libraries from it.');
252 }
253 }
254
255 /// Resolves [target] relative to the location of pub.dart.
256 String relativeToPub(String target) {
257 var scriptPath = new File(new Options().script).fullPathSync();
258
259 // Walk up until we hit the "util(s)" directory. This lets us figure out where
260 // we are if this function is called from pub.dart, or one of the tests,
261 // which also live under "utils", or from the SDK where pub is in "util".
262 var utilDir = path.dirname(scriptPath);
263 while (path.basename(utilDir) != 'utils' &&
264 path.basename(utilDir) != 'util') {
265 if (path.basename(utilDir) == '') {
266 throw new Exception('Could not find path to pub.');
267 }
268 utilDir = path.dirname(utilDir);
269 }
270
271 return path.normalize(path.join(utilDir, 'pub', target));
272 }
273
274 /// A line-by-line stream of standard input.
275 final Stream<String> stdinLines = streamToLines(
276 new ByteStream(stdin).toStringStream());
277
278 /// Displays a message and reads a yes/no confirmation from the user. Returns
279 /// a [Future] that completes to `true` if the user confirms or `false` if they
280 /// do not.
281 ///
282 /// This will automatically append " (y/n)?" to the message, so [message]
283 /// should just be a fragment like, "Are you sure you want to proceed".
284 Future<bool> confirm(String message) {
285 log.fine('Showing confirm message: $message');
286 stdout.write("$message (y/n)? ");
287 return streamFirst(stdinLines)
288 .then((line) => new RegExp(r"^[yY]").hasMatch(line));
289 }
290
291 /// Reads and discards all output from [stream]. Returns a [Future] that
292 /// completes when the stream is closed.
293 Future drainStream(Stream stream) {
294 return stream.fold(null, (x, y) {});
295 }
296
297 /// Returns a [EventSink] that pipes all data to [consumer] and a [Future] that
298 /// will succeed when [EventSink] is closed or fail with any errors that occur
299 /// while writing.
300 Pair<EventSink, Future> consumerToSink(StreamConsumer consumer) {
301 var controller = new StreamController();
302 var done = controller.stream.pipe(consumer);
303 return new Pair<EventSink, Future>(controller.sink, done);
304 }
305
306 // TODO(nweiz): remove this when issue 7786 is fixed.
307 /// Pipes all data and errors from [stream] into [sink]. When [stream] is done,
308 /// the returned [Future] is completed and [sink] is closed if [closeSink] is
309 /// true.
310 ///
311 /// When an error occurs on [stream], that error is passed to [sink]. If
312 /// [cancelOnError] is true, [Future] will be completed successfully and no
313 /// more data or errors will be piped from [stream] to [sink]. If
314 /// [cancelOnError] and [closeSink] are both true, [sink] will then be
315 /// closed.
316 Future store(Stream stream, EventSink sink,
317 {bool cancelOnError: true, closeSink: true}) {
318 var completer = new Completer();
319 stream.listen(sink.add,
320 onError: (e) {
321 sink.addError(e);
322 if (cancelOnError) {
323 completer.complete();
324 if (closeSink) sink.close();
325 }
326 },
327 onDone: () {
328 if (closeSink) sink.close();
329 completer.complete();
330 }, cancelOnError: cancelOnError);
331 return completer.future;
332 }
333
334 /// Spawns and runs the process located at [executable], passing in [args].
335 /// Returns a [Future] that will complete with the results of the process after
336 /// it has ended.
337 ///
338 /// The spawned process will inherit its parent's environment variables. If
339 /// [environment] is provided, that will be used to augment (not replace) the
340 /// the inherited variables.
341 Future<PubProcessResult> runProcess(String executable, List<String> args,
342 {workingDir, Map<String, String> environment}) {
343 return _doProcess(Process.run, executable, args, workingDir, environment)
344 .then((result) {
345 // TODO(rnystrom): Remove this and change to returning one string.
346 List<String> toLines(String output) {
347 var lines = splitLines(output);
348 if (!lines.isEmpty && lines.last == "") lines.removeLast();
349 return lines;
350 }
351
352 var pubResult = new PubProcessResult(toLines(result.stdout),
353 toLines(result.stderr),
354 result.exitCode);
355
356 log.processResult(executable, pubResult);
357 return pubResult;
358 });
359 }
360
361 /// Spawns the process located at [executable], passing in [args]. Returns a
362 /// [Future] that will complete with the [Process] once it's been started.
363 ///
364 /// The spawned process will inherit its parent's environment variables. If
365 /// [environment] is provided, that will be used to augment (not replace) the
366 /// the inherited variables.
367 Future<PubProcess> startProcess(String executable, List<String> args,
368 {workingDir, Map<String, String> environment}) =>
369 _doProcess(Process.start, executable, args, workingDir, environment)
370 .then((process) => new PubProcess(process));
371
372 /// A wrapper around [Process] that exposes `dart:async`-style APIs.
373 class PubProcess {
374 /// The underlying `dart:io` [Process].
375 final Process _process;
376
377 /// The mutable field for [stdin].
378 EventSink<List<int>> _stdin;
379
380 /// The mutable field for [stdinClosed].
381 Future _stdinClosed;
382
383 /// The mutable field for [stdout].
384 ByteStream _stdout;
385
386 /// The mutable field for [stderr].
387 ByteStream _stderr;
388
389 /// The mutable field for [exitCode].
390 Future<int> _exitCode;
391
392 /// The sink used for passing data to the process's standard input stream.
393 /// Errors on this stream are surfaced through [stdinClosed], [stdout],
394 /// [stderr], and [exitCode], which are all members of an [ErrorGroup].
395 EventSink<List<int>> get stdin => _stdin;
396
397 // TODO(nweiz): write some more sophisticated Future machinery so that this
398 // doesn't surface errors from the other streams/futures, but still passes its
399 // unhandled errors to them. Right now it's impossible to recover from a stdin
400 // error and continue interacting with the process.
401 /// A [Future] that completes when [stdin] is closed, either by the user or by
402 /// the process itself.
403 ///
404 /// This is in an [ErrorGroup] with [stdout], [stderr], and [exitCode], so any
405 /// error in process will be passed to it, but won't reach the top-level error
406 /// handler unless nothing has handled it.
407 Future get stdinClosed => _stdinClosed;
408
409 /// The process's standard output stream.
410 ///
411 /// This is in an [ErrorGroup] with [stdinClosed], [stderr], and [exitCode],
412 /// so any error in process will be passed to it, but won't reach the
413 /// top-level error handler unless nothing has handled it.
414 ByteStream get stdout => _stdout;
415
416 /// The process's standard error stream.
417 ///
418 /// This is in an [ErrorGroup] with [stdinClosed], [stdout], and [exitCode],
419 /// so any error in process will be passed to it, but won't reach the
420 /// top-level error handler unless nothing has handled it.
421 ByteStream get stderr => _stderr;
422
423 /// A [Future] that will complete to the process's exit code once the process
424 /// has finished running.
425 ///
426 /// This is in an [ErrorGroup] with [stdinClosed], [stdout], and [stderr], so
427 /// any error in process will be passed to it, but won't reach the top-level
428 /// error handler unless nothing has handled it.
429 Future<int> get exitCode => _exitCode;
430
431 /// Creates a new [PubProcess] wrapping [process].
432 PubProcess(Process process)
433 : _process = process {
434 var errorGroup = new ErrorGroup();
435
436 var pair = consumerToSink(process.stdin);
437 _stdin = pair.first;
438 _stdinClosed = errorGroup.registerFuture(pair.last);
439
440 _stdout = new ByteStream(
441 errorGroup.registerStream(process.stdout));
442 _stderr = new ByteStream(
443 errorGroup.registerStream(process.stderr));
444
445 var exitCodeCompleter = new Completer();
446 _exitCode = errorGroup.registerFuture(exitCodeCompleter.future);
447 _process.exitCode.then((code) => exitCodeCompleter.complete(code));
448 }
449
450 /// Sends [signal] to the underlying process.
451 bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) =>
452 _process.kill(signal);
453 }
454
455 /// Calls [fn] with appropriately modified arguments. [fn] should have the same
456 /// signature as [Process.start], except that the returned [Future] may have a
457 /// type other than [Process].
458 Future _doProcess(Function fn, String executable, List<String> args,
459 String workingDir, Map<String, String> environment) {
460 // TODO(rnystrom): Should dart:io just handle this?
461 // Spawning a process on Windows will not look for the executable in the
462 // system path. So, if executable looks like it needs that (i.e. it doesn't
463 // have any path separators in it), then spawn it through a shell.
464 if ((Platform.operatingSystem == "windows") &&
465 (executable.indexOf('\\') == -1)) {
466 args = flatten(["/c", executable, args]);
467 executable = "cmd";
468 }
469
470 final options = new ProcessOptions();
471 if (workingDir != null) {
472 options.workingDirectory = workingDir;
473 }
474
475 if (environment != null) {
476 options.environment = new Map.from(Platform.environment);
477 environment.forEach((key, value) => options.environment[key] = value);
478 }
479
480 log.process(executable, args);
481
482 return fn(executable, args, options);
483 }
484
485 /// Wraps [input] to provide a timeout. If [input] completes before
486 /// [milliseconds] have passed, then the return value completes in the same way.
487 /// However, if [milliseconds] pass before [input] has completed, it completes
488 /// with a [TimeoutException] with [description] (which should be a fragment
489 /// describing the action that timed out).
490 ///
491 /// Note that timing out will not cancel the asynchronous operation behind
492 /// [input].
493 Future timeout(Future input, int milliseconds, String description) {
494 var completer = new Completer();
495 var timer = new Timer(new Duration(milliseconds: milliseconds), () {
496 completer.completeError(new TimeoutException(
497 'Timed out while $description.'));
498 });
499 input.then((value) {
500 if (completer.isCompleted) return;
501 timer.cancel();
502 completer.complete(value);
503 }).catchError((e) {
504 if (completer.isCompleted) return;
505 timer.cancel();
506 completer.completeError(e);
507 });
508 return completer.future;
509 }
510
511 /// Creates a temporary directory and passes its path to [fn]. Once the [Future]
512 /// returned by [fn] completes, the temporary directory and all its contents
513 /// will be deleted. [fn] can also return `null`, in which case the temporary
514 /// directory is deleted immediately afterwards.
515 ///
516 /// Returns a future that completes to the value that the future returned from
517 /// [fn] completes to.
518 Future withTempDir(Future fn(String path)) {
519 return new Future.sync(() {
520 var tempDir = createTempDir();
521 return new Future.sync(() => fn(tempDir))
522 .whenComplete(() => deleteEntry(tempDir));
523 });
524 }
525
526 /// Extracts a `.tar.gz` file from [stream] to [destination]. Returns whether
527 /// or not the extraction was successful.
528 Future<bool> extractTarGz(Stream<List<int>> stream, String destination) {
529 log.fine("Extracting .tar.gz stream to $destination.");
530
531 if (Platform.operatingSystem == "windows") {
532 return _extractTarGzWindows(stream, destination);
533 }
534
535 return startProcess("tar",
536 ["--extract", "--gunzip", "--directory", destination]).then((process) {
537 // Ignore errors on process.std{out,err}. They'll be passed to
538 // process.exitCode, and we don't want them being top-levelled by
539 // std{out,err}Sink.
540 store(process.stdout.handleError((_) {}), stdout, closeSink: false);
541 store(process.stderr.handleError((_) {}), stderr, closeSink: false);
542 return Future.wait([
543 store(stream, process.stdin),
544 process.exitCode
545 ]);
546 }).then((results) {
547 var exitCode = results[1];
548 if (exitCode != 0) {
549 throw new Exception("Failed to extract .tar.gz stream to $destination "
550 "(exit code $exitCode).");
551 }
552 log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode.");
553 });
554 }
555
556 Future<bool> _extractTarGzWindows(Stream<List<int>> stream,
557 String destination) {
558 // TODO(rnystrom): In the repo's history, there is an older implementation of
559 // this that does everything in memory by piping streams directly together
560 // instead of writing out temp files. The code is simpler, but unfortunately,
561 // 7zip seems to periodically fail when we invoke it from Dart and tell it to
562 // read from stdin instead of a file. Consider resurrecting that version if
563 // we can figure out why it fails.
564
565 // Note: This line of code gets munged by create_sdk.py to be the correct
566 // relative path to 7zip in the SDK.
567 var pathTo7zip = '../../third_party/7zip/7za.exe';
568 var command = relativeToPub(pathTo7zip);
569
570 return withTempDir((tempDir) {
571 // Write the archive to a temp file.
572 var dataFile = path.join(tempDir, 'data.tar.gz');
573 return createFileFromStream(stream, dataFile).then((_) {
574 // 7zip can't unarchive from gzip -> tar -> destination all in one step
575 // first we un-gzip it to a tar file.
576 // Note: Setting the working directory instead of passing in a full file
577 // path because 7zip says "A full path is not allowed here."
578 return runProcess(command, ['e', 'data.tar.gz'], workingDir: tempDir);
579 }).then((result) {
580 if (result.exitCode != 0) {
581 throw new Exception('Could not un-gzip (exit code ${result.exitCode}). '
582 'Error:\n'
583 '${result.stdout.join("\n")}\n'
584 '${result.stderr.join("\n")}');
585 }
586
587 // Find the tar file we just created since we don't know its name.
588 var tarFile = listDir(tempDir).firstWhere(
589 (file) => path.extension(file) == '.tar',
590 orElse: () {
591 throw new FormatException('The gzip file did not contain a tar file.');
592 });
593
594 // Untar the archive into the destination directory.
595 return runProcess(command, ['x', tarFile], workingDir: destination);
596 }).then((result) {
597 if (result.exitCode != 0) {
598 throw new Exception('Could not un-tar (exit code ${result.exitCode}). '
599 'Error:\n'
600 '${result.stdout.join("\n")}\n'
601 '${result.stderr.join("\n")}');
602 }
603 return true;
604 });
605 });
606 }
607
608 /// Create a .tar.gz archive from a list of entries. Each entry can be a
609 /// [String], [Directory], or [File] object. The root of the archive is
610 /// considered to be [baseDir], which defaults to the current working directory.
611 /// Returns a [ByteStream] that will emit the contents of the archive.
612 ByteStream createTarGz(List contents, {baseDir}) {
613 var buffer = new StringBuffer();
614 buffer.write('Creating .tag.gz stream containing:\n');
615 contents.forEach((file) => buffer.write('$file\n'));
616 log.fine(buffer.toString());
617
618 var controller = new StreamController<List<int>>();
619
620 if (baseDir == null) baseDir = path.current;
621 baseDir = path.absolute(baseDir);
622 contents = contents.map((entry) {
623 entry = path.absolute(entry);
624 if (!isBeneath(entry, baseDir)) {
625 throw new ArgumentError('Entry $entry is not inside $baseDir.');
626 }
627 return path.relative(entry, from: baseDir);
628 }).toList();
629
630 if (Platform.operatingSystem != "windows") {
631 var args = ["--create", "--gzip", "--directory", baseDir];
632 args.addAll(contents);
633 // TODO(nweiz): It's possible that enough command-line arguments will make
634 // the process choke, so at some point we should save the arguments to a
635 // file and pass them in via --files-from for tar and -i@filename for 7zip.
636 startProcess("tar", args).then((process) {
637 store(process.stdout, controller);
638 }).catchError((e) {
639 // We don't have to worry about double-signaling here, since the store()
640 // above will only be reached if startProcess succeeds.
641 controller.addError(e);
642 controller.close();
643 });
644 return new ByteStream(controller.stream);
645 }
646
647 withTempDir((tempDir) {
648 // Create the tar file.
649 var tarFile = path.join(tempDir, "intermediate.tar");
650 var args = ["a", "-w$baseDir", tarFile];
651 args.addAll(contents.map((entry) => '-i!"$entry"'));
652
653 // Note: This line of code gets munged by create_sdk.py to be the correct
654 // relative path to 7zip in the SDK.
655 var pathTo7zip = '../../third_party/7zip/7za.exe';
656 var command = relativeToPub(pathTo7zip);
657
658 // We're passing 'baseDir' both as '-w' and setting it as the working
659 // directory explicitly here intentionally. The former ensures that the
660 // files added to the archive have the correct relative path in the archive.
661 // The latter enables relative paths in the "-i" args to be resolved.
662 return runProcess(command, args, workingDir: baseDir).then((_) {
663 // GZIP it. 7zip doesn't support doing both as a single operation. Send
664 // the output to stdout.
665 args = ["a", "unused", "-tgzip", "-so", tarFile];
666 return startProcess(command, args);
667 }).then((process) {
668 // Ignore 7zip's stderr. 7zip writes its normal output to stderr. We don't
669 // want to show that since it's meaningless.
670 //
671 // TODO(rnystrom): Should log the stderr and display it if an actual error
672 // occurs.
673 return store(process.stdout, controller);
674 });
675 }).catchError((e) {
676 // We don't have to worry about double-signaling here, since the store()
677 // above will only be reached if everything succeeds.
678 controller.addError(e);
679 controller.close();
680 });
681 return new ByteStream(controller.stream);
682 }
683
684 /// Exception thrown when an operation times out.
685 class TimeoutException implements Exception {
686 final String message;
687
688 const TimeoutException(this.message);
689
690 String toString() => message;
691 }
692
693 /// Contains the results of invoking a [Process] and waiting for it to complete.
694 class PubProcessResult {
695 final List<String> stdout;
696 final List<String> stderr;
697 final int exitCode;
698
699 const PubProcessResult(this.stdout, this.stderr, this.exitCode);
700
701 bool get success => exitCode == 0;
702 }
703
704 /// Gets a [Uri] for [uri], which can either already be one, or be a [String].
705 Uri _getUri(uri) {
706 if (uri is Uri) return uri;
707 return Uri.parse(uri);
708 }
OLDNEW
« no previous file with comments | « utils/pub/http.dart ('k') | utils/pub/lock_file.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698