Chromium Code Reviews| 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'; | |
| 16 import 'utils.dart'; | 17 import 'utils.dart'; |
| 17 import 'curl_client.dart'; | 18 import 'path.dart' as path; |
| 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 String get currentWorkingDir => new File('.').fullPathSync(); | |
| 26 | |
| 27 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); | 25 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); |
| 28 | 26 |
| 29 /** | 27 /** |
| 30 * Prints the given string to `stderr` on its own line. | 28 * Prints the given string to `stderr` on its own line. |
| 31 */ | 29 */ |
| 32 void printError(value) { | 30 void printError(value) { |
| 33 stderr.writeString(value.toString()); | 31 stderr.writeString(value.toString()); |
| 34 stderr.writeString('\n'); | 32 stderr.writeString('\n'); |
| 35 } | 33 } |
| 36 | 34 |
| 37 | 35 |
| 38 /** | 36 /** |
| 39 * Joins a number of path string parts into a single path. Handles | 37 * Joins a number of path string parts into a single path. Handles |
| 40 * platform-specific path separators. Parts can be [String], [Directory], or | 38 * platform-specific path separators. Parts can be [String], [Directory], or |
| 41 * [File] objects. | 39 * [File] objects. |
| 42 */ | 40 */ |
| 43 String join(part1, [part2, part3, part4]) { | 41 String join(part1, [part2, part3, part4]) { |
| 44 final parts = sanitizePath(part1).split('/'); | 42 part1 = _getPath(part1); |
| 43 if (part2 != null) part2 = _getPath(part2); | |
| 44 if (part3 != null) part3 = _getPath(part3); | |
| 45 if (part4 != null) part4 = _getPath(part4); | |
| 45 | 46 |
| 46 for (final part in [part2, part3, part4]) { | 47 // TODO(nweiz): Don't use "?part" in path.dart. |
| 47 if (part == null) continue; | 48 if (part4 != null) { |
| 48 | 49 return path.join(part1, part2, part3, part4); |
| 49 for (final piece in _getPath(part).split('/')) { | 50 } else if (part3 != null) { |
| 50 if (piece == '..' && parts.length > 0 && | 51 return path.join(part1, part2, part3); |
| 51 parts.last != '.' && parts.last != '..') { | 52 } else if (part2 != null) { |
| 52 parts.removeLast(); | 53 return path.join(part1, part2); |
| 53 } else if (piece != '') { | 54 } else { |
| 54 if (parts.length > 0 && parts.last == '.') { | 55 return path.join(part1); |
| 55 parts.removeLast(); | |
| 56 } | |
| 57 parts.add(piece); | |
| 58 } | |
| 59 } | |
| 60 } | 56 } |
| 61 | |
| 62 return Strings.join(parts, Platform.pathSeparator); | |
| 63 } | 57 } |
| 64 | 58 |
| 65 /// Splits [path] into its individual components. | 59 /// Gets the basename, the file name without any leading directory path, for |
| 66 List<String> splitPath(path) => sanitizePath(path).split('/'); | 60 /// [file], which can either be a [String], [File], or [Directory]. |
| 61 String basename(file) => path.filename(_getPath(file)); | |
| 67 | 62 |
| 68 /** | 63 // TODO(nweiz): move this into path.dart. |
| 69 * Gets the basename, the file name without any leading directory path, for | 64 /// Gets the the leading directory path for [file], which can either be a |
| 70 * [file], which can either be a [String], [File], or [Directory]. | 65 /// [String], [File], or [Directory]. |
| 71 */ | 66 String dirname(file) { |
| 72 // TODO(rnystrom): Copied from file_system (so that we don't have to add | 67 file = _sanitizePath(file); |
| 73 // file_system to the SDK). Should unify. | |
| 74 String basename(file) { | |
| 75 file = sanitizePath(file); | |
| 76 | 68 |
| 77 int lastSlash = file.lastIndexOf('/', file.length); | 69 int lastSlash = file.lastIndexOf('/', file.length); |
| 78 if (lastSlash == -1) { | 70 if (lastSlash == -1) { |
| 79 return file; | |
| 80 } else { | |
| 81 return file.substring(lastSlash + 1); | |
| 82 } | |
| 83 } | |
| 84 | |
| 85 /** | |
| 86 * Gets the the leading directory path for [file], which can either be a | |
| 87 * [String], [File], or [Directory]. | |
| 88 */ | |
| 89 // TODO(nweiz): Copied from file_system (so that we don't have to add | |
| 90 // file_system to the SDK). Should unify. | |
| 91 String dirname(file) { | |
| 92 file = sanitizePath(file); | |
| 93 | |
| 94 int lastSlash = file.lastIndexOf('/', file.length); | |
| 95 if (lastSlash == -1) { | |
| 96 return '.'; | 71 return '.'; |
| 97 } else { | 72 } else { |
| 98 return file.substring(0, lastSlash); | 73 return file.substring(0, lastSlash); |
| 99 } | 74 } |
| 100 } | 75 } |
| 101 | 76 |
| 77 // TODO(nweiz): move this into path.dart. | |
| 78 /// Splits [path] into its individual components. | |
| 79 List<String> splitPath(path) => _sanitizePath(path).split('/'); | |
| 80 | |
| 102 /// Returns whether or not [entry] is nested somewhere within [dir]. This just | 81 /// Returns whether or not [entry] is nested somewhere within [dir]. This just |
| 103 /// performs a path comparison; it doesn't look at the actual filesystem. | 82 /// performs a path comparison; it doesn't look at the actual filesystem. |
| 104 bool isBeneath(entry, dir) => | 83 bool isBeneath(entry, dir) { |
| 105 sanitizePath(entry).startsWith('${sanitizePath(dir)}/'); | 84 var relative = relativeTo(entry, dir); |
| 85 return !path.isAbsolute(relative) && !relative.startsWith('..'); | |
| 86 } | |
| 87 | |
| 88 // TODO(nweiz): move this into path.dart. | |
| 89 /// Returns the path to [target] from [base]. | |
| 90 String relativeTo(target, base) => | |
| 91 new path.Builder(root: base).relative(target); | |
| 106 | 92 |
| 107 /** | 93 /** |
| 108 * Asynchronously determines if [path], which can be a [String] file path, a | 94 * Asynchronously determines if [path], which can be a [String] file path, a |
| 109 * [File], or a [Directory] exists on the file system. Returns a [Future] that | 95 * [File], or a [Directory] exists on the file system. Returns a [Future] that |
| 110 * completes with the result. | 96 * completes with the result. |
| 111 */ | 97 */ |
| 112 Future<bool> exists(path) { | 98 Future<bool> exists(path) { |
| 113 path = _getPath(path); | 99 path = _getPath(path); |
| 114 return Futures.wait([fileExists(path), dirExists(path)]).transform((results) { | 100 return Futures.wait([fileExists(path), dirExists(path)]).transform((results) { |
| 115 return results[0] || results[1]; | 101 return results[0] || results[1]; |
| (...skipping 276 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 392 'Warning: Package "$name" does not have a "lib" directory so you ' | 378 'Warning: Package "$name" does not have a "lib" directory so you ' |
| 393 'will not be able to import any libraries from it.'); | 379 'will not be able to import any libraries from it.'); |
| 394 } | 380 } |
| 395 | 381 |
| 396 return new Future.immediate(to); | 382 return new Future.immediate(to); |
| 397 }); | 383 }); |
| 398 } | 384 } |
| 399 | 385 |
| 400 /// Given [entry] which may be a [String], [File], or [Directory] relative to | 386 /// Given [entry] which may be a [String], [File], or [Directory] relative to |
| 401 /// the current working directory, returns its full canonicalized path. | 387 /// the current working directory, returns its full canonicalized path. |
| 402 String getFullPath(entry) { | 388 String getFullPath(entry) => path.absolute(_getPath(entry)); |
| 403 var path = _getPath(entry); | |
| 404 | |
| 405 // Don't do anything if it's already absolute. | |
| 406 if (isAbsolute(path)) return path; | |
| 407 | |
| 408 // Using Path.join here instead of File().fullPathSync() because the former | |
| 409 // does not require an actual file to exist at that path. | |
| 410 return new Path.fromNative(currentWorkingDir).join(new Path(path)) | |
| 411 .toNativePath(); | |
| 412 } | |
| 413 | 389 |
| 414 /// Returns whether or not [entry] is an absolute path. | 390 /// Returns whether or not [entry] is an absolute path. |
| 415 bool isAbsolute(entry) => _splitAbsolute(entry).first != null; | 391 bool isAbsolute(entry) => path.isAbsolute(_getPath(entry)); |
| 416 | 392 |
| 417 /// Splits [entry] into two components: the absolute path prefix and the | 393 /// Resolves [target] relative to the location of pub.dart. |
| 418 /// remaining path. Takes into account Windows' quirky absolute paths syntaxes. | 394 String relativeToPub(String target) { |
| 419 Pair<String, String> _splitAbsolute(entry) { | |
| 420 var path = _getPath(entry); | |
| 421 | |
| 422 if (Platform.operatingSystem != 'windows') { | |
| 423 return !path.startsWith('/') ? new Pair(null, path) | |
| 424 : new Pair('/', path.substring(1)); | |
| 425 } | |
| 426 | |
| 427 // An absolute path on Windows is either UNC (two leading backslashes), | |
| 428 // or a drive letter followed by a colon and a slash. | |
| 429 var match = new RegExp(r'^(\\\\|[a-zA-Z]:[/\\])').firstMatch(path); | |
| 430 return match == null ? new Pair(null, path) | |
| 431 : new Pair(match.group(0), path.substring(match.end)); | |
| 432 } | |
| 433 | |
| 434 /// Resolves [path] relative to the location of pub.dart. | |
| 435 String relativeToPub(String path) { | |
| 436 var scriptPath = new File(new Options().script).fullPathSync(); | 395 var scriptPath = new File(new Options().script).fullPathSync(); |
| 437 | 396 |
| 438 // Walk up until we hit the "util(s)" directory. This lets us figure out where | 397 // Walk up until we hit the "util(s)" directory. This lets us figure out where |
| 439 // we are if this function is called from pub.dart, or one of the tests, | 398 // we are if this function is called from pub.dart, or one of the tests, |
| 440 // which also live under "utils", or from the SDK where pub is in "util". | 399 // which also live under "utils", or from the SDK where pub is in "util". |
| 441 var utilDir = new Path.fromNative(scriptPath).directoryPath; | 400 var utilDir = dirname(scriptPath); |
| 442 while (utilDir.filename != 'utils' && utilDir.filename != 'util') { | 401 while (basename(utilDir) != 'utils' && basename(utilDir) != 'util') { |
| 443 if (utilDir.filename == '') throw 'Could not find path to pub.'; | 402 if (basename(utilDir) == '') throw 'Could not find path to pub.'; |
| 444 utilDir = utilDir.directoryPath; | 403 utilDir = dirname(utilDir); |
| 445 } | 404 } |
| 446 | 405 |
| 447 return utilDir.append('pub').append(path).canonicalize().toNativePath(); | 406 return path.normalize(join(utilDir, 'pub', target)); |
| 448 } | 407 } |
| 449 | 408 |
| 450 /// A StringInputStream reading from stdin. | 409 /// A StringInputStream reading from stdin. |
| 451 final _stringStdin = new StringInputStream(stdin); | 410 final _stringStdin = new StringInputStream(stdin); |
| 452 | 411 |
| 453 /// Returns a single line read from a [StringInputStream]. By default, reads | 412 /// Returns a single line read from a [StringInputStream]. By default, reads |
| 454 /// from stdin. | 413 /// from stdin. |
| 455 /// | 414 /// |
| 456 /// A [StringInputStream] passed to this should have no callbacks registered. | 415 /// A [StringInputStream] passed to this should have no callbacks registered. |
| 457 Future<String> readLine([StringInputStream stream]) { | 416 Future<String> readLine([StringInputStream stream]) { |
| (...skipping 397 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 855 if (result.exitCode != 0) { | 814 if (result.exitCode != 0) { |
| 856 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n' | 815 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n' |
| 857 '${Strings.join(result.stdout, "\n")}\n' | 816 '${Strings.join(result.stdout, "\n")}\n' |
| 858 '${Strings.join(result.stderr, "\n")}'; | 817 '${Strings.join(result.stderr, "\n")}'; |
| 859 } | 818 } |
| 860 // Find the tar file we just created since we don't know its name. | 819 // Find the tar file we just created since we don't know its name. |
| 861 return listDir(tempDir); | 820 return listDir(tempDir); |
| 862 }).chain((files) { | 821 }).chain((files) { |
| 863 var tarFile; | 822 var tarFile; |
| 864 for (var file in files) { | 823 for (var file in files) { |
| 865 if (new Path(file).extension == 'tar') { | 824 if (path.extension(file) == '.tar') { |
| 866 tarFile = file; | 825 tarFile = file; |
| 867 break; | 826 break; |
| 868 } | 827 } |
| 869 } | 828 } |
| 870 | 829 |
| 871 if (tarFile == null) throw 'The gzip file did not contain a tar file.'; | 830 if (tarFile == null) throw 'The gzip file did not contain a tar file.'; |
| 872 | 831 |
| 873 // Untar the archive into the destination directory. | 832 // Untar the archive into the destination directory. |
| 874 return runProcess(command, ['x', tarFile], workingDir: destination); | 833 return runProcess(command, ['x', tarFile], workingDir: destination); |
| 875 }).chain((result) { | 834 }).chain((result) { |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 887 | 846 |
| 888 /// Create a .tar.gz archive from a list of entries. Each entry can be a | 847 /// Create a .tar.gz archive from a list of entries. Each entry can be a |
| 889 /// [String], [Directory], or [File] object. The root of the archive is | 848 /// [String], [Directory], or [File] object. The root of the archive is |
| 890 /// considered to be [baseDir], which defaults to the current working directory. | 849 /// considered to be [baseDir], which defaults to the current working directory. |
| 891 /// Returns an [InputStream] that will emit the contents of the archive. | 850 /// Returns an [InputStream] that will emit the contents of the archive. |
| 892 InputStream createTarGz(List contents, {baseDir}) { | 851 InputStream createTarGz(List contents, {baseDir}) { |
| 893 // TODO(nweiz): Propagate errors to the returned stream (including non-zero | 852 // TODO(nweiz): Propagate errors to the returned stream (including non-zero |
| 894 // exit codes). See issue 3657. | 853 // exit codes). See issue 3657. |
| 895 var stream = new ListInputStream(); | 854 var stream = new ListInputStream(); |
| 896 | 855 |
| 897 if (baseDir == null) baseDir = currentWorkingDir; | 856 if (baseDir == null) baseDir = path.current; |
| 898 baseDir = getFullPath(baseDir); | 857 baseDir = getFullPath(baseDir); |
| 899 contents = contents.map((entry) { | 858 contents = contents.map((entry) { |
| 900 entry = getFullPath(entry); | 859 entry = getFullPath(entry); |
| 901 if (!isBeneath(entry, baseDir)) { | 860 if (!isBeneath(entry, baseDir)) { |
| 902 throw 'Entry $entry is not inside $baseDir.'; | 861 throw 'Entry $entry is not inside $baseDir.'; |
| 903 } | 862 } |
| 904 return new Path.fromNative(entry).relativeTo(new Path.fromNative(baseDir)) | 863 return relativeTo(entry, baseDir); |
| 905 .toNativePath(); | |
| 906 }); | 864 }); |
| 907 | 865 |
| 908 if (Platform.operatingSystem != "windows") { | 866 if (Platform.operatingSystem != "windows") { |
| 909 var args = ["--create", "--gzip", "--directory", baseDir]; | 867 var args = ["--create", "--gzip", "--directory", baseDir]; |
| 910 args.addAll(contents.map(_getPath)); | 868 args.addAll(contents.map(_getPath)); |
| 911 // TODO(nweiz): It's possible that enough command-line arguments will make | 869 // TODO(nweiz): It's possible that enough command-line arguments will make |
| 912 // the process choke, so at some point we should save the arguments to a | 870 // the process choke, so at some point we should save the arguments to a |
| 913 // file and pass them in via --files-from for tar and -i@filename for 7zip. | 871 // file and pass them in via --files-from for tar and -i@filename for 7zip. |
| 914 startProcess("tar", args).then((process) { | 872 startProcess("tar", args).then((process) { |
| 915 pipeInputToInput(process.stdout, stream); | 873 pipeInputToInput(process.stdout, stream); |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 989 */ | 947 */ |
| 990 String _getPath(entry) { | 948 String _getPath(entry) { |
| 991 if (entry is String) return entry; | 949 if (entry is String) return entry; |
| 992 if (entry is File) return entry.name; | 950 if (entry is File) return entry.name; |
| 993 if (entry is Directory) return entry.path; | 951 if (entry is Directory) return entry.path; |
| 994 throw 'Entry $entry is not a supported type.'; | 952 throw 'Entry $entry is not a supported type.'; |
| 995 } | 953 } |
| 996 | 954 |
| 997 /// Gets the path string for [entry], normalizing backslashes to forward slashes | 955 /// Gets the path string for [entry], normalizing backslashes to forward slashes |
| 998 /// on Windows. | 956 /// on Windows. |
| 999 String sanitizePath(entry) { | 957 String _sanitizePath(entry) { |
| 1000 entry = _getPath(entry); | 958 entry = _getPath(entry); |
| 1001 if (Platform.operatingSystem != 'windows') return entry; | 959 if (Platform.operatingSystem != 'windows') return entry; |
| 1002 | 960 |
| 1003 var split = _splitAbsolute(entry); | 961 var split = _splitAbsolute(entry); |
| 1004 if (split.first == null) return split.last.replaceAll('\\', '/'); | 962 if (split.first == null) return split.last.replaceAll('\\', '/'); |
| 1005 | 963 |
| 1006 // For absolute Windows paths, we don't want the prefix (either "\\" or e.g. | 964 // For absolute Windows paths, we don't want the prefix (either "\\" or e.g. |
| 1007 // "C:\") to look like a normal path component, so we ensure that it only | 965 // "C:\") to look like a normal path component, so we ensure that it only |
| 1008 // contains backslashes. | 966 // contains backslashes. |
| 1009 return '${split.first.replaceAll('/', '\\')}' | 967 return '${split.first.replaceAll('/', '\\')}' |
| 1010 '${split.last.replaceAll('\\', '/')}'; | 968 '${split.last.replaceAll('\\', '/')}'; |
| 1011 } | 969 } |
| 1012 | 970 |
| 971 /// Splits [entry] into two components: the absolute path prefix and the | |
| 972 /// remaining path. Takes into account Windows' quirky absolute paths syntaxes. | |
|
Bob Nystrom
2012/12/08 03:36:53
Add a TODO to move this (or something similar) int
nweiz
2012/12/08 03:43:45
Done.
| |
| 973 Pair<String, String> _splitAbsolute(entry) { | |
| 974 var path = _getPath(entry); | |
| 975 | |
| 976 if (Platform.operatingSystem != 'windows') { | |
| 977 return !path.startsWith('/') ? new Pair(null, path) | |
| 978 : new Pair('/', path.substring(1)); | |
| 979 } | |
| 980 | |
| 981 // An absolute path on Windows is either UNC (two leading backslashes), | |
| 982 // or a drive letter followed by a colon and a slash. | |
| 983 var match = new RegExp(r'^(\\\\|[a-zA-Z]:[/\\])').firstMatch(path); | |
| 984 return match == null ? new Pair(null, path) | |
| 985 : new Pair(match.group(0), path.substring(match.end)); | |
| 986 } | |
| 987 | |
| 1013 /** | 988 /** |
| 1014 * Gets a [Directory] for [entry], which can either already be one, or be a | 989 * Gets a [Directory] for [entry], which can either already be one, or be a |
| 1015 * [String]. | 990 * [String]. |
| 1016 */ | 991 */ |
| 1017 Directory _getDirectory(entry) { | 992 Directory _getDirectory(entry) { |
| 1018 if (entry is Directory) return entry; | 993 if (entry is Directory) return entry; |
| 1019 return new Directory(entry); | 994 return new Directory(entry); |
| 1020 } | 995 } |
| 1021 | 996 |
| 1022 /** | 997 /** |
| 1023 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 998 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
| 1024 */ | 999 */ |
| 1025 Uri _getUri(uri) { | 1000 Uri _getUri(uri) { |
| 1026 if (uri is Uri) return uri; | 1001 if (uri is Uri) return uri; |
| 1027 return new Uri.fromString(uri); | 1002 return new Uri.fromString(uri); |
| 1028 } | 1003 } |
| OLD | NEW |