Index: utils/pub/io.dart |
diff --git a/utils/pub/io.dart b/utils/pub/io.dart |
deleted file mode 100644 |
index 12121e7e1aedf6d9eeab76e951de2ec647a14c17..0000000000000000000000000000000000000000 |
--- a/utils/pub/io.dart |
+++ /dev/null |
@@ -1,708 +0,0 @@ |
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-/// Helper functionality to make working with IO easier. |
-library io; |
- |
-import 'dart:async'; |
-import 'dart:io'; |
-import 'dart:isolate'; |
-import 'dart:json'; |
-import 'dart:uri'; |
- |
-import 'package:pathos/path.dart' as path; |
-import 'package:http/http.dart' show ByteStream; |
-import 'error_group.dart'; |
-import 'exit_codes.dart' as exit_codes; |
-import 'log.dart' as log; |
-import 'utils.dart'; |
- |
-export 'package:http/http.dart' show ByteStream; |
- |
-/// Returns whether or not [entry] is nested somewhere within [dir]. This just |
-/// performs a path comparison; it doesn't look at the actual filesystem. |
-bool isBeneath(String entry, String dir) { |
- var relative = path.relative(entry, from: dir); |
- return !path.isAbsolute(relative) && path.split(relative)[0] != '..'; |
-} |
- |
-/// Determines if a file or directory exists at [path]. |
-bool entryExists(String path) => |
- dirExists(path) || fileExists(path) || linkExists(path); |
- |
-/// Returns whether [link] exists on the file system. This will return `true` |
-/// for any symlink, regardless of what it points at or whether it's broken. |
-bool linkExists(String link) => new Link(link).existsSync(); |
- |
-/// Returns whether [file] exists on the file system. This will return `true` |
-/// for a symlink only if that symlink is unbroken and points to a file. |
-bool fileExists(String file) => new File(file).existsSync(); |
- |
-/// Reads the contents of the text file [file]. |
-String readTextFile(String file) => |
- new File(file).readAsStringSync(encoding: Encoding.UTF_8); |
- |
-/// Reads the contents of the binary file [file]. |
-List<int> readBinaryFile(String file) { |
- log.io("Reading binary file $file."); |
- var contents = new File(file).readAsBytesSync(); |
- log.io("Read ${contents.length} bytes from $file."); |
- return contents; |
-} |
- |
-/// Creates [file] and writes [contents] to it. |
-/// |
-/// If [dontLogContents] is true, the contents of the file will never be logged. |
-String writeTextFile(String file, String contents, {dontLogContents: false}) { |
- // Sanity check: don't spew a huge file. |
- log.io("Writing ${contents.length} characters to text file $file."); |
- if (!dontLogContents && contents.length < 1024 * 1024) { |
- log.fine("Contents:\n$contents"); |
- } |
- |
- new File(file).writeAsStringSync(contents); |
- return file; |
-} |
- |
-/// Creates [file] and writes [contents] to it. |
-String writeBinaryFile(String file, List<int> contents) { |
- log.io("Writing ${contents.length} bytes to binary file $file."); |
- new File(file).openSync(mode: FileMode.WRITE) |
- ..writeFromSync(contents) |
- ..closeSync(); |
- log.fine("Wrote text file $file."); |
- return file; |
-} |
- |
-/// Writes [stream] to a new file at path [file]. Will replace any file already |
-/// at that path. Completes when the file is done being written. |
-Future<String> createFileFromStream(Stream<List<int>> stream, String file) { |
- log.io("Creating $file from stream."); |
- |
- return stream.pipe(new File(file).openWrite()).then((_) { |
- log.fine("Created $file from stream."); |
- return file; |
- }); |
-} |
- |
-/// Creates a directory [dir]. |
-String createDir(String dir) { |
- new Directory(dir).createSync(); |
- return dir; |
-} |
- |
-/// Ensures that [dirPath] and all its parent directories exist. If they don't |
-/// exist, creates them. |
-String ensureDir(String dirPath) { |
- log.fine("Ensuring directory $dirPath exists."); |
- var dir = new Directory(dirPath); |
- if (dirPath == '.' || dirExists(dirPath)) return dirPath; |
- |
- ensureDir(path.dirname(dirPath)); |
- |
- try { |
- createDir(dirPath); |
- } on DirectoryIOException catch (ex) { |
- // Error 17 means the directory already exists (or 183 on Windows). |
- if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) { |
- log.fine("Got 'already exists' error when creating directory."); |
- } else { |
- throw ex; |
- } |
- } |
- |
- return dirPath; |
-} |
- |
-/// Creates a temp directory whose name will be based on [dir] with a unique |
-/// suffix appended to it. If [dir] is not provided, a temp directory will be |
-/// created in a platform-dependent temporary location. Returns the path of the |
-/// created directory. |
-String createTempDir([dir = '']) { |
- var tempDir = new Directory(dir).createTempSync(); |
- log.io("Created temp directory ${tempDir.path}"); |
- return tempDir.path; |
-} |
- |
-/// Lists the contents of [dir]. If [recursive] is `true`, lists subdirectory |
-/// contents (defaults to `false`). If [includeHidden] is `true`, includes files |
-/// and directories beginning with `.` (defaults to `false`). |
-/// |
-/// The returned paths are guaranteed to begin with [dir]. |
-List<String> listDir(String dir, {bool recursive: false, |
- bool includeHidden: false}) { |
- List<String> doList(String dir, Set<String> listedDirectories) { |
- var contents = <String>[]; |
- |
- // Avoid recursive symlinks. |
- var resolvedPath = new File(dir).fullPathSync(); |
- if (listedDirectories.contains(resolvedPath)) return []; |
- |
- listedDirectories = new Set<String>.from(listedDirectories); |
- listedDirectories.add(resolvedPath); |
- |
- log.io("Listing directory $dir."); |
- |
- var children = <String>[]; |
- for (var entity in new Directory(dir).listSync()) { |
- if (!includeHidden && path.basename(entity.path).startsWith('.')) { |
- continue; |
- } |
- |
- contents.add(entity.path); |
- if (entity is Directory) { |
- // TODO(nweiz): don't manually recurse once issue 4794 is fixed. |
- // Note that once we remove the manual recursion, we'll need to |
- // explicitly filter out files in hidden directories. |
- if (recursive) { |
- children.addAll(doList(entity.path, listedDirectories)); |
- } |
- } |
- } |
- |
- log.fine("Listed directory $dir:\n${contents.join('\n')}"); |
- contents.addAll(children); |
- return contents; |
- } |
- |
- return doList(dir, new Set<String>()); |
-} |
- |
-/// Returns whether [dir] exists on the file system. This will return `true` for |
-/// a symlink only if that symlink is unbroken and points to a directory. |
-bool dirExists(String dir) => new Directory(dir).existsSync(); |
- |
-/// Deletes whatever's at [path], whether it's a file, directory, or symlink. If |
-/// it's a directory, it will be deleted recursively. |
-void deleteEntry(String path) { |
- if (linkExists(path)) { |
- log.io("Deleting link $path."); |
- new Link(path).deleteSync(); |
- } else if (dirExists(path)) { |
- log.io("Deleting directory $path."); |
- new Directory(path).deleteSync(recursive: true); |
- } else if (fileExists(path)) { |
- log.io("Deleting file $path."); |
- new File(path).deleteSync(); |
- } |
-} |
- |
-/// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a |
-/// new empty directory will be created. |
-void cleanDir(String dir) { |
- if (entryExists(dir)) deleteEntry(dir); |
- createDir(dir); |
-} |
- |
-/// Renames (i.e. moves) the directory [from] to [to]. |
-void renameDir(String from, String to) { |
- log.io("Renaming directory $from to $to."); |
- new Directory(from).renameSync(to); |
-} |
- |
-/// Creates a new symlink at path [symlink] that points to [target]. Returns a |
-/// [Future] which completes to the path to the symlink file. |
-/// |
-/// If [relative] is true, creates a symlink with a relative path from the |
-/// symlink to the target. Otherwise, uses the [target] path unmodified. |
-/// |
-/// Note that on Windows, only directories may be symlinked to. |
-void createSymlink(String target, String symlink, |
- {bool relative: false}) { |
- if (relative) { |
- // Relative junction points are not supported on Windows. Instead, just |
- // make sure we have a clean absolute path because it will interpret a |
- // relative path to be relative to the cwd, not the symlink, and will be |
- // confused by forward slashes. |
- if (Platform.operatingSystem == 'windows') { |
- target = path.normalize(path.absolute(target)); |
- } else { |
- target = path.normalize( |
- path.relative(target, from: path.dirname(symlink))); |
- } |
- } |
- |
- log.fine("Creating $symlink pointing to $target"); |
- new Link(symlink).createSync(target); |
-} |
- |
-/// Creates a new symlink that creates an alias at [symlink] that points to the |
-/// `lib` directory of package [target]. If [target] does not have a `lib` |
-/// directory, this shows a warning if appropriate and then does nothing. |
-/// |
-/// If [relative] is true, creates a symlink with a relative path from the |
-/// symlink to the target. Otherwise, uses the [target] path unmodified. |
-void createPackageSymlink(String name, String target, String symlink, |
- {bool isSelfLink: false, bool relative: false}) { |
- // See if the package has a "lib" directory. |
- target = path.join(target, 'lib'); |
- log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); |
- if (dirExists(target)) { |
- createSymlink(target, symlink, relative: relative); |
- return; |
- } |
- |
- // It's OK for the self link (i.e. the root package) to not have a lib |
- // directory since it may just be a leaf application that only has |
- // code in bin or web. |
- if (!isSelfLink) { |
- log.warning('Warning: Package "$name" does not have a "lib" directory so ' |
- 'you will not be able to import any libraries from it.'); |
- } |
-} |
- |
-/// Resolves [target] relative to the location of pub.dart. |
-String relativeToPub(String target) { |
- var scriptPath = new File(new Options().script).fullPathSync(); |
- |
- // Walk up until we hit the "util(s)" directory. This lets us figure out where |
- // we are if this function is called from pub.dart, or one of the tests, |
- // which also live under "utils", or from the SDK where pub is in "util". |
- var utilDir = path.dirname(scriptPath); |
- while (path.basename(utilDir) != 'utils' && |
- path.basename(utilDir) != 'util') { |
- if (path.basename(utilDir) == '') { |
- throw new Exception('Could not find path to pub.'); |
- } |
- utilDir = path.dirname(utilDir); |
- } |
- |
- return path.normalize(path.join(utilDir, 'pub', target)); |
-} |
- |
-/// A line-by-line stream of standard input. |
-final Stream<String> stdinLines = streamToLines( |
- new ByteStream(stdin).toStringStream()); |
- |
-/// Displays a message and reads a yes/no confirmation from the user. Returns |
-/// a [Future] that completes to `true` if the user confirms or `false` if they |
-/// do not. |
-/// |
-/// This will automatically append " (y/n)?" to the message, so [message] |
-/// should just be a fragment like, "Are you sure you want to proceed". |
-Future<bool> confirm(String message) { |
- log.fine('Showing confirm message: $message'); |
- stdout.write("$message (y/n)? "); |
- return streamFirst(stdinLines) |
- .then((line) => new RegExp(r"^[yY]").hasMatch(line)); |
-} |
- |
-/// Reads and discards all output from [stream]. Returns a [Future] that |
-/// completes when the stream is closed. |
-Future drainStream(Stream stream) { |
- return stream.fold(null, (x, y) {}); |
-} |
- |
-/// Returns a [EventSink] that pipes all data to [consumer] and a [Future] that |
-/// will succeed when [EventSink] is closed or fail with any errors that occur |
-/// while writing. |
-Pair<EventSink, Future> consumerToSink(StreamConsumer consumer) { |
- var controller = new StreamController(); |
- var done = controller.stream.pipe(consumer); |
- return new Pair<EventSink, Future>(controller.sink, done); |
-} |
- |
-// TODO(nweiz): remove this when issue 7786 is fixed. |
-/// Pipes all data and errors from [stream] into [sink]. When [stream] is done, |
-/// the returned [Future] is completed and [sink] is closed if [closeSink] is |
-/// true. |
-/// |
-/// When an error occurs on [stream], that error is passed to [sink]. If |
-/// [cancelOnError] is true, [Future] will be completed successfully and no |
-/// more data or errors will be piped from [stream] to [sink]. If |
-/// [cancelOnError] and [closeSink] are both true, [sink] will then be |
-/// closed. |
-Future store(Stream stream, EventSink sink, |
- {bool cancelOnError: true, closeSink: true}) { |
- var completer = new Completer(); |
- stream.listen(sink.add, |
- onError: (e) { |
- sink.addError(e); |
- if (cancelOnError) { |
- completer.complete(); |
- if (closeSink) sink.close(); |
- } |
- }, |
- onDone: () { |
- if (closeSink) sink.close(); |
- completer.complete(); |
- }, cancelOnError: cancelOnError); |
- return completer.future; |
-} |
- |
-/// Spawns and runs the process located at [executable], passing in [args]. |
-/// Returns a [Future] that will complete with the results of the process after |
-/// it has ended. |
-/// |
-/// The spawned process will inherit its parent's environment variables. If |
-/// [environment] is provided, that will be used to augment (not replace) the |
-/// the inherited variables. |
-Future<PubProcessResult> runProcess(String executable, List<String> args, |
- {workingDir, Map<String, String> environment}) { |
- return _doProcess(Process.run, executable, args, workingDir, environment) |
- .then((result) { |
- // TODO(rnystrom): Remove this and change to returning one string. |
- List<String> toLines(String output) { |
- var lines = splitLines(output); |
- if (!lines.isEmpty && lines.last == "") lines.removeLast(); |
- return lines; |
- } |
- |
- var pubResult = new PubProcessResult(toLines(result.stdout), |
- toLines(result.stderr), |
- result.exitCode); |
- |
- log.processResult(executable, pubResult); |
- return pubResult; |
- }); |
-} |
- |
-/// Spawns the process located at [executable], passing in [args]. Returns a |
-/// [Future] that will complete with the [Process] once it's been started. |
-/// |
-/// The spawned process will inherit its parent's environment variables. If |
-/// [environment] is provided, that will be used to augment (not replace) the |
-/// the inherited variables. |
-Future<PubProcess> startProcess(String executable, List<String> args, |
- {workingDir, Map<String, String> environment}) => |
- _doProcess(Process.start, executable, args, workingDir, environment) |
- .then((process) => new PubProcess(process)); |
- |
-/// A wrapper around [Process] that exposes `dart:async`-style APIs. |
-class PubProcess { |
- /// The underlying `dart:io` [Process]. |
- final Process _process; |
- |
- /// The mutable field for [stdin]. |
- EventSink<List<int>> _stdin; |
- |
- /// The mutable field for [stdinClosed]. |
- Future _stdinClosed; |
- |
- /// The mutable field for [stdout]. |
- ByteStream _stdout; |
- |
- /// The mutable field for [stderr]. |
- ByteStream _stderr; |
- |
- /// The mutable field for [exitCode]. |
- Future<int> _exitCode; |
- |
- /// The sink used for passing data to the process's standard input stream. |
- /// Errors on this stream are surfaced through [stdinClosed], [stdout], |
- /// [stderr], and [exitCode], which are all members of an [ErrorGroup]. |
- EventSink<List<int>> get stdin => _stdin; |
- |
- // TODO(nweiz): write some more sophisticated Future machinery so that this |
- // doesn't surface errors from the other streams/futures, but still passes its |
- // unhandled errors to them. Right now it's impossible to recover from a stdin |
- // error and continue interacting with the process. |
- /// A [Future] that completes when [stdin] is closed, either by the user or by |
- /// the process itself. |
- /// |
- /// This is in an [ErrorGroup] with [stdout], [stderr], and [exitCode], so any |
- /// error in process will be passed to it, but won't reach the top-level error |
- /// handler unless nothing has handled it. |
- Future get stdinClosed => _stdinClosed; |
- |
- /// The process's standard output stream. |
- /// |
- /// This is in an [ErrorGroup] with [stdinClosed], [stderr], and [exitCode], |
- /// so any error in process will be passed to it, but won't reach the |
- /// top-level error handler unless nothing has handled it. |
- ByteStream get stdout => _stdout; |
- |
- /// The process's standard error stream. |
- /// |
- /// This is in an [ErrorGroup] with [stdinClosed], [stdout], and [exitCode], |
- /// so any error in process will be passed to it, but won't reach the |
- /// top-level error handler unless nothing has handled it. |
- ByteStream get stderr => _stderr; |
- |
- /// A [Future] that will complete to the process's exit code once the process |
- /// has finished running. |
- /// |
- /// This is in an [ErrorGroup] with [stdinClosed], [stdout], and [stderr], so |
- /// any error in process will be passed to it, but won't reach the top-level |
- /// error handler unless nothing has handled it. |
- Future<int> get exitCode => _exitCode; |
- |
- /// Creates a new [PubProcess] wrapping [process]. |
- PubProcess(Process process) |
- : _process = process { |
- var errorGroup = new ErrorGroup(); |
- |
- var pair = consumerToSink(process.stdin); |
- _stdin = pair.first; |
- _stdinClosed = errorGroup.registerFuture(pair.last); |
- |
- _stdout = new ByteStream( |
- errorGroup.registerStream(process.stdout)); |
- _stderr = new ByteStream( |
- errorGroup.registerStream(process.stderr)); |
- |
- var exitCodeCompleter = new Completer(); |
- _exitCode = errorGroup.registerFuture(exitCodeCompleter.future); |
- _process.exitCode.then((code) => exitCodeCompleter.complete(code)); |
- } |
- |
- /// Sends [signal] to the underlying process. |
- bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) => |
- _process.kill(signal); |
-} |
- |
-/// Calls [fn] with appropriately modified arguments. [fn] should have the same |
-/// signature as [Process.start], except that the returned [Future] may have a |
-/// type other than [Process]. |
-Future _doProcess(Function fn, String executable, List<String> args, |
- String workingDir, Map<String, String> environment) { |
- // TODO(rnystrom): Should dart:io just handle this? |
- // Spawning a process on Windows will not look for the executable in the |
- // system path. So, if executable looks like it needs that (i.e. it doesn't |
- // have any path separators in it), then spawn it through a shell. |
- if ((Platform.operatingSystem == "windows") && |
- (executable.indexOf('\\') == -1)) { |
- args = flatten(["/c", executable, args]); |
- executable = "cmd"; |
- } |
- |
- final options = new ProcessOptions(); |
- if (workingDir != null) { |
- options.workingDirectory = workingDir; |
- } |
- |
- if (environment != null) { |
- options.environment = new Map.from(Platform.environment); |
- environment.forEach((key, value) => options.environment[key] = value); |
- } |
- |
- log.process(executable, args); |
- |
- return fn(executable, args, options); |
-} |
- |
-/// Wraps [input] to provide a timeout. If [input] completes before |
-/// [milliseconds] have passed, then the return value completes in the same way. |
-/// However, if [milliseconds] pass before [input] has completed, it completes |
-/// with a [TimeoutException] with [description] (which should be a fragment |
-/// describing the action that timed out). |
-/// |
-/// Note that timing out will not cancel the asynchronous operation behind |
-/// [input]. |
-Future timeout(Future input, int milliseconds, String description) { |
- var completer = new Completer(); |
- var timer = new Timer(new Duration(milliseconds: milliseconds), () { |
- completer.completeError(new TimeoutException( |
- 'Timed out while $description.')); |
- }); |
- input.then((value) { |
- if (completer.isCompleted) return; |
- timer.cancel(); |
- completer.complete(value); |
- }).catchError((e) { |
- if (completer.isCompleted) return; |
- timer.cancel(); |
- completer.completeError(e); |
- }); |
- return completer.future; |
-} |
- |
-/// Creates a temporary directory and passes its path to [fn]. Once the [Future] |
-/// returned by [fn] completes, the temporary directory and all its contents |
-/// will be deleted. [fn] can also return `null`, in which case the temporary |
-/// directory is deleted immediately afterwards. |
-/// |
-/// Returns a future that completes to the value that the future returned from |
-/// [fn] completes to. |
-Future withTempDir(Future fn(String path)) { |
- return new Future.sync(() { |
- var tempDir = createTempDir(); |
- return new Future.sync(() => fn(tempDir)) |
- .whenComplete(() => deleteEntry(tempDir)); |
- }); |
-} |
- |
-/// Extracts a `.tar.gz` file from [stream] to [destination]. Returns whether |
-/// or not the extraction was successful. |
-Future<bool> extractTarGz(Stream<List<int>> stream, String destination) { |
- log.fine("Extracting .tar.gz stream to $destination."); |
- |
- if (Platform.operatingSystem == "windows") { |
- return _extractTarGzWindows(stream, destination); |
- } |
- |
- return startProcess("tar", |
- ["--extract", "--gunzip", "--directory", destination]).then((process) { |
- // Ignore errors on process.std{out,err}. They'll be passed to |
- // process.exitCode, and we don't want them being top-levelled by |
- // std{out,err}Sink. |
- store(process.stdout.handleError((_) {}), stdout, closeSink: false); |
- store(process.stderr.handleError((_) {}), stderr, closeSink: false); |
- return Future.wait([ |
- store(stream, process.stdin), |
- process.exitCode |
- ]); |
- }).then((results) { |
- var exitCode = results[1]; |
- if (exitCode != 0) { |
- throw new Exception("Failed to extract .tar.gz stream to $destination " |
- "(exit code $exitCode)."); |
- } |
- log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode."); |
- }); |
-} |
- |
-Future<bool> _extractTarGzWindows(Stream<List<int>> stream, |
- String destination) { |
- // TODO(rnystrom): In the repo's history, there is an older implementation of |
- // this that does everything in memory by piping streams directly together |
- // instead of writing out temp files. The code is simpler, but unfortunately, |
- // 7zip seems to periodically fail when we invoke it from Dart and tell it to |
- // read from stdin instead of a file. Consider resurrecting that version if |
- // we can figure out why it fails. |
- |
- // Note: This line of code gets munged by create_sdk.py to be the correct |
- // relative path to 7zip in the SDK. |
- var pathTo7zip = '../../third_party/7zip/7za.exe'; |
- var command = relativeToPub(pathTo7zip); |
- |
- return withTempDir((tempDir) { |
- // Write the archive to a temp file. |
- var dataFile = path.join(tempDir, 'data.tar.gz'); |
- return createFileFromStream(stream, dataFile).then((_) { |
- // 7zip can't unarchive from gzip -> tar -> destination all in one step |
- // first we un-gzip it to a tar file. |
- // Note: Setting the working directory instead of passing in a full file |
- // path because 7zip says "A full path is not allowed here." |
- return runProcess(command, ['e', 'data.tar.gz'], workingDir: tempDir); |
- }).then((result) { |
- if (result.exitCode != 0) { |
- throw new Exception('Could not un-gzip (exit code ${result.exitCode}). ' |
- 'Error:\n' |
- '${result.stdout.join("\n")}\n' |
- '${result.stderr.join("\n")}'); |
- } |
- |
- // Find the tar file we just created since we don't know its name. |
- var tarFile = listDir(tempDir).firstWhere( |
- (file) => path.extension(file) == '.tar', |
- orElse: () { |
- throw new FormatException('The gzip file did not contain a tar file.'); |
- }); |
- |
- // Untar the archive into the destination directory. |
- return runProcess(command, ['x', tarFile], workingDir: destination); |
- }).then((result) { |
- if (result.exitCode != 0) { |
- throw new Exception('Could not un-tar (exit code ${result.exitCode}). ' |
- 'Error:\n' |
- '${result.stdout.join("\n")}\n' |
- '${result.stderr.join("\n")}'); |
- } |
- return true; |
- }); |
- }); |
-} |
- |
-/// Create a .tar.gz archive from a list of entries. Each entry can be a |
-/// [String], [Directory], or [File] object. The root of the archive is |
-/// considered to be [baseDir], which defaults to the current working directory. |
-/// Returns a [ByteStream] that will emit the contents of the archive. |
-ByteStream createTarGz(List contents, {baseDir}) { |
- var buffer = new StringBuffer(); |
- buffer.write('Creating .tag.gz stream containing:\n'); |
- contents.forEach((file) => buffer.write('$file\n')); |
- log.fine(buffer.toString()); |
- |
- var controller = new StreamController<List<int>>(); |
- |
- if (baseDir == null) baseDir = path.current; |
- baseDir = path.absolute(baseDir); |
- contents = contents.map((entry) { |
- entry = path.absolute(entry); |
- if (!isBeneath(entry, baseDir)) { |
- throw new ArgumentError('Entry $entry is not inside $baseDir.'); |
- } |
- return path.relative(entry, from: baseDir); |
- }).toList(); |
- |
- if (Platform.operatingSystem != "windows") { |
- var args = ["--create", "--gzip", "--directory", baseDir]; |
- args.addAll(contents); |
- // TODO(nweiz): It's possible that enough command-line arguments will make |
- // the process choke, so at some point we should save the arguments to a |
- // file and pass them in via --files-from for tar and -i@filename for 7zip. |
- startProcess("tar", args).then((process) { |
- store(process.stdout, controller); |
- }).catchError((e) { |
- // We don't have to worry about double-signaling here, since the store() |
- // above will only be reached if startProcess succeeds. |
- controller.addError(e); |
- controller.close(); |
- }); |
- return new ByteStream(controller.stream); |
- } |
- |
- withTempDir((tempDir) { |
- // Create the tar file. |
- var tarFile = path.join(tempDir, "intermediate.tar"); |
- var args = ["a", "-w$baseDir", tarFile]; |
- args.addAll(contents.map((entry) => '-i!"$entry"')); |
- |
- // Note: This line of code gets munged by create_sdk.py to be the correct |
- // relative path to 7zip in the SDK. |
- var pathTo7zip = '../../third_party/7zip/7za.exe'; |
- var command = relativeToPub(pathTo7zip); |
- |
- // We're passing 'baseDir' both as '-w' and setting it as the working |
- // directory explicitly here intentionally. The former ensures that the |
- // files added to the archive have the correct relative path in the archive. |
- // The latter enables relative paths in the "-i" args to be resolved. |
- return runProcess(command, args, workingDir: baseDir).then((_) { |
- // GZIP it. 7zip doesn't support doing both as a single operation. Send |
- // the output to stdout. |
- args = ["a", "unused", "-tgzip", "-so", tarFile]; |
- return startProcess(command, args); |
- }).then((process) { |
- // Ignore 7zip's stderr. 7zip writes its normal output to stderr. We don't |
- // want to show that since it's meaningless. |
- // |
- // TODO(rnystrom): Should log the stderr and display it if an actual error |
- // occurs. |
- return store(process.stdout, controller); |
- }); |
- }).catchError((e) { |
- // We don't have to worry about double-signaling here, since the store() |
- // above will only be reached if everything succeeds. |
- controller.addError(e); |
- controller.close(); |
- }); |
- return new ByteStream(controller.stream); |
-} |
- |
-/// Exception thrown when an operation times out. |
-class TimeoutException implements Exception { |
- final String message; |
- |
- const TimeoutException(this.message); |
- |
- String toString() => message; |
-} |
- |
-/// Contains the results of invoking a [Process] and waiting for it to complete. |
-class PubProcessResult { |
- final List<String> stdout; |
- final List<String> stderr; |
- final int exitCode; |
- |
- const PubProcessResult(this.stdout, this.stderr, this.exitCode); |
- |
- bool get success => exitCode == 0; |
-} |
- |
-/// Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
-Uri _getUri(uri) { |
- if (uri is Uri) return uri; |
- return Uri.parse(uri); |
-} |