| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /// Helper functionality to make working with IO easier. | 5 /// Helper functionality to make working with IO easier. |
| 6 library pub.io; | 6 library pub.io; |
| 7 | 7 |
| 8 import 'dart:async'; | 8 import 'dart:async'; |
| 9 import 'dart:collection'; | 9 import 'dart:collection'; |
| 10 import 'dart:convert'; | 10 import 'dart:convert'; |
| (...skipping 811 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 822 executable = "cmd"; | 822 executable = "cmd"; |
| 823 } | 823 } |
| 824 | 824 |
| 825 log.process(executable, args, workingDir == null ? '.' : workingDir); | 825 log.process(executable, args, workingDir == null ? '.' : workingDir); |
| 826 | 826 |
| 827 return fn(executable, args, | 827 return fn(executable, args, |
| 828 workingDirectory: workingDir, | 828 workingDirectory: workingDir, |
| 829 environment: environment); | 829 environment: environment); |
| 830 } | 830 } |
| 831 | 831 |
| 832 /// Wraps [input], an asynchronous network operation to provide a timeout. | |
| 833 /// | |
| 834 /// If [input] completes before [milliseconds] have passed, then the return | |
| 835 /// value completes in the same way. However, if [milliseconds] pass before | |
| 836 /// [input] has completed, it completes with a [TimeoutException] with | |
| 837 /// [description] (which should be a fragment describing the action that timed | |
| 838 /// out). | |
| 839 /// | |
| 840 /// [url] is the URL being accessed asynchronously. | |
| 841 /// | |
| 842 /// Note that timing out will not cancel the asynchronous operation behind | |
| 843 /// [input]. | |
| 844 Future timeout(Future input, int milliseconds, Uri url, String description) { | |
| 845 // TODO(nwiez): Replace this with [Future.timeout]. | |
| 846 var completer = new Completer(); | |
| 847 var duration = new Duration(milliseconds: milliseconds); | |
| 848 var timer = new Timer(duration, () { | |
| 849 // Include the duration ourselves in the message instead of passing it to | |
| 850 // TimeoutException since we show nicer output. | |
| 851 var message = 'Timed out after ${niceDuration(duration)} while ' | |
| 852 '$description.'; | |
| 853 | |
| 854 if (url.host == "pub.dartlang.org" || | |
| 855 url.host == "storage.googleapis.com") { | |
| 856 message += "\nThis is likely a transient error. Please try again later."; | |
| 857 } | |
| 858 | |
| 859 completer.completeError(new TimeoutException(message), new Chain.current()); | |
| 860 }); | |
| 861 input.then((value) { | |
| 862 if (completer.isCompleted) return; | |
| 863 timer.cancel(); | |
| 864 completer.complete(value); | |
| 865 }).catchError((e, stackTrace) { | |
| 866 if (completer.isCompleted) return; | |
| 867 timer.cancel(); | |
| 868 completer.completeError(e, stackTrace); | |
| 869 }); | |
| 870 return completer.future; | |
| 871 } | |
| 872 | |
| 873 /// Creates a temporary directory and passes its path to [fn]. | 832 /// Creates a temporary directory and passes its path to [fn]. |
| 874 /// | 833 /// |
| 875 /// Once the [Future] returned by [fn] completes, the temporary directory and | 834 /// Once the [Future] returned by [fn] completes, the temporary directory and |
| 876 /// all its contents are deleted. [fn] can also return `null`, in which case | 835 /// all its contents are deleted. [fn] can also return `null`, in which case |
| 877 /// the temporary directory is deleted immediately afterwards. | 836 /// the temporary directory is deleted immediately afterwards. |
| 878 /// | 837 /// |
| 879 /// Returns a future that completes to the value that the future returned from | 838 /// Returns a future that completes to the value that the future returned from |
| 880 /// [fn] completes to. | 839 /// [fn] completes to. |
| 881 Future withTempDir(Future fn(String path)) { | 840 Future withTempDir(Future fn(String path)) { |
| 882 return new Future.sync(() { | 841 return new Future.sync(() { |
| 883 var tempDir = createSystemTempDir(); | 842 var tempDir = createSystemTempDir(); |
| 884 return new Future.sync(() => fn(tempDir)) | 843 return new Future.sync(() => fn(tempDir)) |
| 885 .whenComplete(() => deleteEntry(tempDir)); | 844 .whenComplete(() => deleteEntry(tempDir)); |
| 886 }); | 845 }); |
| 887 } | 846 } |
| 888 | 847 |
| 889 /// Binds an [HttpServer] to [host] and [port]. | 848 /// Binds an [HttpServer] to [host] and [port]. |
| 890 /// | 849 /// |
| 891 /// If [host] is "localhost", this will automatically listen on both the IPv4 | 850 /// If [host] is "localhost", this will automatically listen on both the IPv4 |
| 892 /// and IPv6 loopback addresses. | 851 /// and IPv6 loopback addresses. |
| 893 Future<HttpServer> bindServer(String host, int port) { | 852 Future<HttpServer> bindServer(String host, int port) { |
| 894 if (host == 'localhost') return HttpMultiServer.loopback(port); | 853 if (host == 'localhost') return HttpMultiServer.loopback(port); |
| 895 return HttpServer.bind(host, port); | 854 return HttpServer.bind(host, port); |
| 896 } | 855 } |
| 897 | 856 |
| 898 /// Extracts a `.tar.gz` file from [stream] to [destination]. | 857 /// Extracts a `.tar.gz` file from [stream] to [destination]. |
| 899 /// | 858 Future extractTarGz(Stream<List<int>> stream, String destination) async { |
| 900 /// Returns whether or not the extraction was successful. | |
| 901 Future<bool> extractTarGz(Stream<List<int>> stream, String destination) { | |
| 902 log.fine("Extracting .tar.gz stream to $destination."); | 859 log.fine("Extracting .tar.gz stream to $destination."); |
| 903 | 860 |
| 904 if (Platform.operatingSystem == "windows") { | 861 if (Platform.operatingSystem == "windows") { |
| 905 return _extractTarGzWindows(stream, destination); | 862 return await _extractTarGzWindows(stream, destination); |
| 906 } | 863 } |
| 907 | 864 |
| 908 var args = ["--extract", "--gunzip", "--directory", destination]; | 865 var args = ["--extract", "--gunzip", "--directory", destination]; |
| 909 if (_noUnknownKeyword) { | 866 if (_noUnknownKeyword) { |
| 910 // BSD tar (the default on OS X) can insert strange headers to a tarfile | 867 // BSD tar (the default on OS X) can insert strange headers to a tarfile |
| 911 // that GNU tar (the default on Linux) is unable to understand. This will | 868 // that GNU tar (the default on Linux) is unable to understand. This will |
| 912 // cause GNU tar to emit a number of harmless but scary-looking warnings | 869 // cause GNU tar to emit a number of harmless but scary-looking warnings |
| 913 // which are silenced by this flag. | 870 // which are silenced by this flag. |
| 914 args.insert(0, "--warning=no-unknown-keyword"); | 871 args.insert(0, "--warning=no-unknown-keyword"); |
| 915 } | 872 } |
| 916 | 873 |
| 917 return startProcess("tar", args).then((process) { | 874 var process = await startProcess("tar", args); |
| 918 // Ignore errors on process.std{out,err}. They'll be passed to | 875 |
| 919 // process.exitCode, and we don't want them being top-levelled by | 876 // Ignore errors on process.std{out,err}. They'll be passed to |
| 920 // std{out,err}Sink. | 877 // process.exitCode, and we don't want them being top-levelled by |
| 921 store(process.stdout.handleError((_) {}), stdout, closeSink: false); | 878 // std{out,err}Sink. |
| 922 store(process.stderr.handleError((_) {}), stderr, closeSink: false); | 879 store(process.stdout.handleError((_) {}), stdout, closeSink: false); |
| 923 return Future.wait([ | 880 store(process.stderr.handleError((_) {}), stderr, closeSink: false); |
| 924 store(stream, process.stdin), | 881 var results = await Future.wait([ |
| 925 process.exitCode | 882 store(stream, process.stdin), |
| 926 ]); | 883 process.exitCode |
| 927 }).then((results) { | 884 ]); |
| 928 var exitCode = results[1]; | 885 |
| 929 if (exitCode != exit_codes.SUCCESS) { | 886 var exitCode = results[1]; |
| 930 throw new Exception("Failed to extract .tar.gz stream to $destination " | 887 if (exitCode != exit_codes.SUCCESS) { |
| 931 "(exit code $exitCode)."); | 888 throw new Exception("Failed to extract .tar.gz stream to $destination " |
| 932 } | 889 "(exit code $exitCode)."); |
| 933 log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode."); | 890 } |
| 934 }); | 891 log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode."); |
| 935 } | 892 } |
| 936 | 893 |
| 937 /// Whether to include "--warning=no-unknown-keyword" when invoking tar. | 894 /// Whether to include "--warning=no-unknown-keyword" when invoking tar. |
| 938 /// | 895 /// |
| 939 /// This flag quiets warnings that come from opening OS X-generated tarballs on | 896 /// This flag quiets warnings that come from opening OS X-generated tarballs on |
| 940 /// Linux, but only GNU tar >= 1.26 supports it. | 897 /// Linux, but only GNU tar >= 1.26 supports it. |
| 941 final bool _noUnknownKeyword = _computeNoUnknownKeyword(); | 898 final bool _noUnknownKeyword = _computeNoUnknownKeyword(); |
| 942 bool _computeNoUnknownKeyword() { | 899 bool _computeNoUnknownKeyword() { |
| 943 if (!Platform.isLinux) return false; | 900 if (!Platform.isLinux) return false; |
| 944 var result = Process.runSync("tar", ["--version"]); | 901 var result = Process.runSync("tar", ["--version"]); |
| 945 if (result.exitCode != 0) { | 902 if (result.exitCode != 0) { |
| 946 throw new ApplicationException( | 903 throw new ApplicationException( |
| 947 "Failed to run tar (exit code ${result.exitCode}):\n${result.stderr}"); | 904 "Failed to run tar (exit code ${result.exitCode}):\n${result.stderr}"); |
| 948 } | 905 } |
| 949 | 906 |
| 950 var match = new RegExp(r"^tar \(GNU tar\) (\d+).(\d+)\n") | 907 var match = new RegExp(r"^tar \(GNU tar\) (\d+).(\d+)\n") |
| 951 .firstMatch(result.stdout); | 908 .firstMatch(result.stdout); |
| 952 if (match == null) return false; | 909 if (match == null) return false; |
| 953 | 910 |
| 954 var major = int.parse(match[1]); | 911 var major = int.parse(match[1]); |
| 955 var minor = int.parse(match[2]); | 912 var minor = int.parse(match[2]); |
| 956 return major >= 2 || (major == 1 && minor >= 23); | 913 return major >= 2 || (major == 1 && minor >= 23); |
| 957 } | 914 } |
| 958 | 915 |
| 959 final String pathTo7zip = (() { | 916 final String pathTo7zip = (() { |
| 960 if (!runningFromDartRepo) return sdkAssetPath(path.join('7zip', '7za.exe')); | 917 if (!runningFromDartRepo) return sdkAssetPath(path.join('7zip', '7za.exe')); |
| 961 return path.join(dartRepoRoot, 'third_party', '7zip', '7za.exe'); | 918 return path.join(dartRepoRoot, 'third_party', '7zip', '7za.exe'); |
| 962 })(); | 919 })(); |
| 963 | 920 |
| 964 Future<bool> _extractTarGzWindows(Stream<List<int>> stream, | 921 Future _extractTarGzWindows(Stream<List<int>> stream, String destination) { |
| 965 String destination) { | |
| 966 // TODO(rnystrom): In the repo's history, there is an older implementation of | 922 // TODO(rnystrom): In the repo's history, there is an older implementation of |
| 967 // this that does everything in memory by piping streams directly together | 923 // this that does everything in memory by piping streams directly together |
| 968 // instead of writing out temp files. The code is simpler, but unfortunately, | 924 // instead of writing out temp files. The code is simpler, but unfortunately, |
| 969 // 7zip seems to periodically fail when we invoke it from Dart and tell it to | 925 // 7zip seems to periodically fail when we invoke it from Dart and tell it to |
| 970 // read from stdin instead of a file. Consider resurrecting that version if | 926 // read from stdin instead of a file. Consider resurrecting that version if |
| 971 // we can figure out why it fails. | 927 // we can figure out why it fails. |
| 972 | 928 |
| 973 return withTempDir((tempDir) { | 929 return withTempDir((tempDir) async { |
| 974 // Write the archive to a temp file. | 930 // Write the archive to a temp file. |
| 975 var dataFile = path.join(tempDir, 'data.tar.gz'); | 931 var dataFile = path.join(tempDir, 'data.tar.gz'); |
| 976 return createFileFromStream(stream, dataFile).then((_) { | 932 await createFileFromStream(stream, dataFile); |
| 977 // 7zip can't unarchive from gzip -> tar -> destination all in one step | |
| 978 // first we un-gzip it to a tar file. | |
| 979 // Note: Setting the working directory instead of passing in a full file | |
| 980 // path because 7zip says "A full path is not allowed here." | |
| 981 return runProcess(pathTo7zip, ['e', 'data.tar.gz'], workingDir: tempDir); | |
| 982 }).then((result) { | |
| 983 if (result.exitCode != exit_codes.SUCCESS) { | |
| 984 throw new Exception('Could not un-gzip (exit code ${result.exitCode}). ' | |
| 985 'Error:\n' | |
| 986 '${result.stdout.join("\n")}\n' | |
| 987 '${result.stderr.join("\n")}'); | |
| 988 } | |
| 989 | 933 |
| 990 // Find the tar file we just created since we don't know its name. | 934 // 7zip can't unarchive from gzip -> tar -> destination all in one step |
| 991 var tarFile = listDir(tempDir).firstWhere( | 935 // first we un-gzip it to a tar file. |
| 992 (file) => path.extension(file) == '.tar', | 936 // Note: Setting the working directory instead of passing in a full file |
| 993 orElse: () { | 937 // path because 7zip says "A full path is not allowed here." |
| 994 throw new FormatException('The gzip file did not contain a tar file.'); | 938 var unzipResult = await runProcess(pathTo7zip, ['e', 'data.tar.gz'], |
| 995 }); | 939 workingDir: tempDir); |
| 996 | 940 |
| 997 // Untar the archive into the destination directory. | 941 if (unzipResult.exitCode != exit_codes.SUCCESS) { |
| 998 return runProcess(pathTo7zip, ['x', tarFile], workingDir: destination); | 942 throw new Exception( |
| 999 }).then((result) { | 943 'Could not un-gzip (exit code ${unzipResult.exitCode}). Error:\n' |
| 1000 if (result.exitCode != exit_codes.SUCCESS) { | 944 '${unzipResult.stdout.join("\n")}\n' |
| 1001 throw new Exception('Could not un-tar (exit code ${result.exitCode}). ' | 945 '${unzipResult.stderr.join("\n")}'); |
| 1002 'Error:\n' | 946 } |
| 1003 '${result.stdout.join("\n")}\n' | 947 |
| 1004 '${result.stderr.join("\n")}'); | 948 // Find the tar file we just created since we don't know its name. |
| 1005 } | 949 var tarFile = listDir(tempDir).firstWhere( |
| 1006 return true; | 950 (file) => path.extension(file) == '.tar', |
| 951 orElse: () { |
| 952 throw new FormatException('The gzip file did not contain a tar file.'); |
| 1007 }); | 953 }); |
| 954 |
| 955 // Untar the archive into the destination directory. |
| 956 var untarResult = await runProcess(pathTo7zip, ['x', tarFile], |
| 957 workingDir: destination); |
| 958 if (untarResult.exitCode != exit_codes.SUCCESS) { |
| 959 throw new Exception( |
| 960 'Could not un-tar (exit code ${untarResult.exitCode}). Error:\n' |
| 961 '${untarResult.stdout.join("\n")}\n' |
| 962 '${untarResult.stderr.join("\n")}'); |
| 963 } |
| 1008 }); | 964 }); |
| 1009 } | 965 } |
| 1010 | 966 |
| 1011 /// Create a .tar.gz archive from a list of entries. | 967 /// Create a .tar.gz archive from a list of entries. |
| 1012 /// | 968 /// |
| 1013 /// Each entry can be a [String], [Directory], or [File] object. The root of | 969 /// Each entry can be a [String], [Directory], or [File] object. The root of |
| 1014 /// the archive is considered to be [baseDir], which defaults to the current | 970 /// the archive is considered to be [baseDir], which defaults to the current |
| 1015 /// working directory. | 971 /// working directory. |
| 1016 /// | 972 /// |
| 1017 /// Returns a [ByteStream] that emits the contents of the archive. | 973 /// Returns a [ByteStream] that emits the contents of the archive. |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1093 | 1049 |
| 1094 // TODO(rnystrom): Remove this and change to returning one string. | 1050 // TODO(rnystrom): Remove this and change to returning one string. |
| 1095 static List<String> _toLines(String output) { | 1051 static List<String> _toLines(String output) { |
| 1096 var lines = splitLines(output); | 1052 var lines = splitLines(output); |
| 1097 if (!lines.isEmpty && lines.last == "") lines.removeLast(); | 1053 if (!lines.isEmpty && lines.last == "") lines.removeLast(); |
| 1098 return lines; | 1054 return lines; |
| 1099 } | 1055 } |
| 1100 | 1056 |
| 1101 bool get success => exitCode == exit_codes.SUCCESS; | 1057 bool get success => exitCode == exit_codes.SUCCESS; |
| 1102 } | 1058 } |
| OLD | NEW |