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 |