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 |