OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, 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 library package_config.discovery; |
| 6 |
| 7 import "dart:async"; |
| 8 import "dart:io"; |
| 9 import "dart:typed_data" show Uint8List; |
| 10 |
| 11 import "package:path/path.dart" as path; |
| 12 |
| 13 import "packages.dart"; |
| 14 import "packages_file.dart" as pkgfile show parse; |
| 15 import "src/packages_impl.dart"; |
| 16 import "src/packages_io_impl.dart"; |
| 17 |
| 18 /// Reads a package resolution file and creates a [Packages] object from it. |
| 19 /// |
| 20 /// The [packagesFile] must exist and be loadable. |
| 21 /// Currently that means the URI must have a `file`, `http` or `https` scheme, |
| 22 /// and that the file can be loaded and its contents parsed correctly. |
| 23 /// |
| 24 /// If the [loader] is provided, it is used to fetch non-`file` URIs, and |
| 25 /// it can support other schemes or set up more complex HTTP requests. |
| 26 /// |
| 27 /// This function can be used to load an explicitly configured package |
| 28 /// resolution file, for example one specified using a `--packages` |
| 29 /// command-line parameter. |
| 30 Future<Packages> loadPackagesFile(Uri packagesFile, |
| 31 {Future<List<int>> loader(Uri uri)}) { |
| 32 Packages parseBytes(List<int> bytes) { |
| 33 Map<String, Uri> packageMap = pkgfile.parse(bytes, packagesFile); |
| 34 return new MapPackages(packageMap); |
| 35 } |
| 36 if (packagesFile.scheme == "file") { |
| 37 File file = new File.fromUri(packagesFile); |
| 38 return file.readAsBytes().then(parseBytes); |
| 39 } |
| 40 if (loader == null) { |
| 41 return _httpGet(packagesFile).then(parseBytes); |
| 42 } |
| 43 return loader(packagesFile).then(parseBytes); |
| 44 } |
| 45 |
| 46 |
| 47 /// Create a [Packages] object for a package directory. |
| 48 /// |
| 49 /// The [packagesDir] URI should refer to a directory. |
| 50 /// Package names are resolved as relative to sub-directories of the |
| 51 /// package directory. |
| 52 /// |
| 53 /// This function can be used for explicitly configured package directories, |
| 54 /// for example one specified using a `--package-root` comand-line parameter. |
| 55 Packages getPackagesDirectory(Uri packagesDir) { |
| 56 if (packagesDir.scheme == "file") { |
| 57 Directory directory = new Directory.fromUri(packagesDir); |
| 58 return new FilePackagesDirectoryPackages(directory); |
| 59 } |
| 60 if (!packagesDir.path.endsWith('/')) { |
| 61 packagesDir = packagesDir.replace(path: packagesDir.path + '/'); |
| 62 } |
| 63 return new NonFilePackagesDirectoryPackages(packagesDir); |
| 64 } |
| 65 |
| 66 |
| 67 /// Discover the package configuration for a Dart script. |
| 68 /// |
| 69 /// The [baseUri] points to either the Dart script or its directory. |
| 70 /// A package resolution strategy is found by going through the following steps, |
| 71 /// and stopping when something is found. |
| 72 /// |
| 73 /// * Check if a `.packages` file exists in the same directory. |
| 74 /// * If `baseUri`'s scheme is not `file`, then assume a `packages` directory |
| 75 /// in the same directory, and resolve packages relative to that. |
| 76 /// * If `baseUri`'s scheme *is* `file`: |
| 77 /// * Check if a `packages` directory exists. |
| 78 /// * Otherwise check each successive parent directory of `baseUri` for a |
| 79 /// `.packages` file. |
| 80 /// |
| 81 /// If any of these tests succeed, a `Packages` class is returned. |
| 82 /// Returns the constant [noPackages] if no resolution strategy is found. |
| 83 /// |
| 84 /// This function currently only supports `file`, `http` and `https` URIs. |
| 85 /// It needs to be able to load a `.packages` file from the URI, so only |
| 86 /// recognized schemes are accepted. |
| 87 /// |
| 88 /// To support other schemes, or more complex HTTP requests, |
| 89 /// an optional [loader] function can be supplied. |
| 90 /// It's called to load the `.packages` file for a non-`file` scheme. |
| 91 /// The loader function returns the *contents* of the file |
| 92 /// identified by the URI it's given. |
| 93 /// The content should be a UTF-8 encoded `.packages` file, and must return an |
| 94 /// error future if loading fails for any reason. |
| 95 Future<Packages> findPackages(Uri baseUri, |
| 96 {Future<List<int>> loader(Uri unsupportedUri)}) { |
| 97 if (baseUri.scheme == "file") { |
| 98 return new Future<Packages>.sync(() => findPackagesFromFile(baseUri)); |
| 99 } else if (loader != null) { |
| 100 return findPackagesFromNonFile(baseUri, loader: loader); |
| 101 } else if (baseUri.scheme == "http" || baseUri.scheme == "https") { |
| 102 return findPackagesFromNonFile(baseUri, loader: _httpGet); |
| 103 } else { |
| 104 return new Future<Packages>.value(Packages.noPackages); |
| 105 } |
| 106 } |
| 107 |
| 108 /// Find the location of the package resolution file/directory for a Dart file. |
| 109 /// |
| 110 /// Checks for a `.packages` file in the [workingDirectory]. |
| 111 /// If not found, checks for a `packages` directory in the same directory. |
| 112 /// If still not found, starts checking parent directories for |
| 113 /// `.packages` until reaching the root directory. |
| 114 /// |
| 115 /// Returns a [File] object of a `.packages` file if one is found, or a |
| 116 /// [Directory] object for the `packages/` directory if that is found. |
| 117 FileSystemEntity _findPackagesFile(String workingDirectory) { |
| 118 var dir = new Directory(workingDirectory); |
| 119 if (!dir.isAbsolute) dir = dir.absolute; |
| 120 if (!dir.existsSync()) { |
| 121 throw new ArgumentError.value( |
| 122 workingDirectory, "workingDirectory", "Directory does not exist."); |
| 123 } |
| 124 File checkForConfigFile(Directory directory) { |
| 125 assert(directory.isAbsolute); |
| 126 var file = new File(path.join(directory.path, ".packages")); |
| 127 if (file.existsSync()) return file; |
| 128 return null; |
| 129 } |
| 130 // Check for $cwd/.packages |
| 131 var packagesCfgFile = checkForConfigFile(dir); |
| 132 if (packagesCfgFile != null) return packagesCfgFile; |
| 133 // Check for $cwd/packages/ |
| 134 var packagesDir = new Directory(path.join(dir.path, "packages")); |
| 135 if (packagesDir.existsSync()) return packagesDir; |
| 136 // Check for cwd(/..)+/.packages |
| 137 var parentDir = dir.parent; |
| 138 while (parentDir.path != dir.path) { |
| 139 packagesCfgFile = checkForConfigFile(parentDir); |
| 140 if (packagesCfgFile != null) break; |
| 141 dir = parentDir; |
| 142 parentDir = dir.parent; |
| 143 } |
| 144 return packagesCfgFile; |
| 145 } |
| 146 |
| 147 /// Finds a package resolution strategy for a local Dart script. |
| 148 /// |
| 149 /// The [fileBaseUri] points to either a Dart script or the directory of the |
| 150 /// script. The `fileBaseUri` must be a `file:` URI. |
| 151 /// |
| 152 /// This function first tries to locate a `.packages` file in the `fileBaseUri` |
| 153 /// directory. If that is not found, it instead checks for the presence of |
| 154 /// a `packages/` directory in the same place. |
| 155 /// If that also fails, it starts checking parent directories for a `.packages` |
| 156 /// file, and stops if it finds it. |
| 157 /// Otherwise it gives up and returns [Packages.noPackages]. |
| 158 Packages findPackagesFromFile(Uri fileBaseUri) { |
| 159 Uri baseDirectoryUri = fileBaseUri; |
| 160 if (!fileBaseUri.path.endsWith('/')) { |
| 161 baseDirectoryUri = baseDirectoryUri.resolve("."); |
| 162 } |
| 163 String baseDirectoryPath = baseDirectoryUri.toFilePath(); |
| 164 FileSystemEntity location = _findPackagesFile(baseDirectoryPath); |
| 165 if (location == null) return Packages.noPackages; |
| 166 if (location is File) { |
| 167 List<int> fileBytes = location.readAsBytesSync(); |
| 168 Map<String, Uri> map = |
| 169 pkgfile.parse(fileBytes, new Uri.file(location.path)); |
| 170 return new MapPackages(map); |
| 171 } |
| 172 assert(location is Directory); |
| 173 return new FilePackagesDirectoryPackages(location); |
| 174 } |
| 175 |
| 176 /// Finds a package resolution strategy for a Dart script. |
| 177 /// |
| 178 /// The [nonFileUri] points to either a Dart script or the directory of the |
| 179 /// script. |
| 180 /// The [nonFileUri] should not be a `file:` URI since the algorithm for |
| 181 /// finding a package resolution strategy is more elaborate for `file:` URIs. |
| 182 /// In that case, use [findPackagesFromFile]. |
| 183 /// |
| 184 /// This function first tries to locate a `.packages` file in the [nonFileUri] |
| 185 /// directory. If that is not found, it instead assumes a `packages/` directory |
| 186 /// in the same place. |
| 187 /// |
| 188 /// By default, this function only works for `http:` and `https:` URIs. |
| 189 /// To support other schemes, a loader must be provided, which is used to |
| 190 /// try to load the `.packages` file. The loader should return the contents |
| 191 /// of the requested `.packages` file as bytes, which will be assumed to be |
| 192 /// UTF-8 encoded. |
| 193 Future<Packages> findPackagesFromNonFile(Uri nonFileUri, |
| 194 {Future<List<int>> loader(Uri name)}) { |
| 195 if (loader == null) loader = _httpGet; |
| 196 Uri packagesFileUri = nonFileUri.resolve(".packages"); |
| 197 return loader(packagesFileUri).then((List<int> fileBytes) { |
| 198 Map<String, Uri> map = pkgfile.parse(fileBytes, packagesFileUri); |
| 199 return new MapPackages(map); |
| 200 }, onError: (_) { |
| 201 // Didn't manage to load ".packages". Assume a "packages/" directory. |
| 202 Uri packagesDirectoryUri = nonFileUri.resolve("packages/"); |
| 203 return new NonFilePackagesDirectoryPackages(packagesDirectoryUri); |
| 204 }); |
| 205 } |
| 206 |
| 207 /// Fetches a file over http. |
| 208 Future<List<int>> _httpGet(Uri uri) async { |
| 209 HttpClient client = new HttpClient(); |
| 210 HttpClientRequest request = await client.getUrl(uri); |
| 211 HttpClientResponse response = await request.close(); |
| 212 if (response.statusCode != HttpStatus.OK) { |
| 213 throw 'Failure getting $uri: ' |
| 214 '${response.statusCode} ${response.reasonPhrase}'; |
| 215 } |
| 216 List<List<int>> splitContent = await response.toList(); |
| 217 int totalLength = 0; |
| 218 for (var list in splitContent) { |
| 219 totalLength += list.length; |
| 220 } |
| 221 Uint8List result = new Uint8List(totalLength); |
| 222 int offset = 0; |
| 223 for (List<int> contentPart in splitContent) { |
| 224 result.setRange(offset, offset + contentPart.length, contentPart); |
| 225 offset += contentPart.length; |
| 226 } |
| 227 return result; |
| 228 } |
OLD | NEW |