Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(34)

Side by Side Diff: pkg/analyzer/lib/src/summary/pub_summary.dart

Issue 2220703002: Initial implementation of pub summary manager. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Parse without context, write atomically. Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2016, 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 import 'dart:async';
6 import 'dart:collection';
7 import 'dart:core' hide Resource;
8
9 import 'package:analyzer/dart/ast/ast.dart';
10 import 'package:analyzer/dart/ast/token.dart';
11 import 'package:analyzer/file_system/file_system.dart';
12 import 'package:analyzer/src/dart/scanner/reader.dart';
13 import 'package:analyzer/src/dart/scanner/scanner.dart';
14 import 'package:analyzer/src/generated/engine.dart';
15 import 'package:analyzer/src/generated/error.dart';
16 import 'package:analyzer/src/generated/parser.dart';
17 import 'package:analyzer/src/generated/source.dart';
18 import 'package:analyzer/src/summary/format.dart';
19 import 'package:analyzer/src/summary/idl.dart';
20 import 'package:analyzer/src/summary/package_bundle_reader.dart'
21 show ResynthesizerResultProvider;
22 import 'package:analyzer/src/summary/summarize_ast.dart'
23 show serializeAstUnlinked;
24 import 'package:analyzer/src/summary/summarize_elements.dart'
25 show PackageBundleAssembler;
26 import 'package:analyzer/src/util/fast_uri.dart';
27 import 'package:path/path.dart' as pathos;
28
29 /**
30 * A package in the pub cache.
31 */
32 class PubPackage {
33 final String name;
34 final Folder libFolder;
35
36 PubPackage(this.name, this.libFolder);
37
38 Folder get folder => libFolder.parent;
39
40 @override
41 int get hashCode => libFolder.hashCode;
42
43 @override
44 bool operator ==(other) {
45 return other is PubPackage && other.libFolder == libFolder;
46 }
47
48 @override
49 String toString() => '($name in $folder)';
50 }
51
52 /**
53 * Class that manages summaries for pub packages.
54 *
55 * The client should call [getLinkedBundles] after creating a new
56 * [AnalysisContext] and configuring its source factory, but before computing
57 * any analysis results. The returned linked bundles can be used to create and
58 * configure [ResynthesizerResultProvider] for the context.
59 */
60 class PubSummaryManager {
61 static const UNLINKED_BUNDLE_FILE_NAME = 'unlinked.ds';
62
63 final ResourceProvider resourceProvider;
64
65 /**
66 * The name of the temporary file that is used for atomic writes.
67 */
68 final String tempFileName;
69
70 /**
71 * The map from [PubPackage]s to their unlinked [PackageBundle]s in the pub
72 * cache.
73 */
74 final Map<PubPackage, PackageBundle> unlinkedBundleMap =
75 new HashMap<PubPackage, PackageBundle>();
76
77 /**
78 * The set of packages to compute unlinked summaries for.
79 */
80 final Set<PubPackage> packagesToComputeUnlinked = new Set<PubPackage>();
81
82 /**
83 * The set of already processed packages, which we have already checked
84 * for their unlinked bundle existence, or scheduled its computing.
85 */
86 final Set<PubPackage> seenPackages = new Set<PubPackage>();
87
88 /**
89 * The [Completer] that completes when computing of all scheduled unlinked
90 * bundles is complete.
91 */
92 Completer _onUnlinkedCompleteCompleter;
93
94 PubSummaryManager(this.resourceProvider, this.tempFileName);
95
96 /**
97 * The [Future] that completes when computing of all scheduled unlinked
98 * bundles is complete.
99 */
100 Future get onUnlinkedComplete {
101 if (packagesToComputeUnlinked.isEmpty) {
102 return new Future.value();
103 }
104 _onUnlinkedCompleteCompleter ??= new Completer();
105 return _onUnlinkedCompleteCompleter.future;
106 }
107
108 /**
109 * Return the [pathos.Context] corresponding to the [resourceProvider].
110 */
111 pathos.Context get pathContext => resourceProvider.pathContext;
112
113 /**
114 * Return the list of linked [PackageBundle]s that can be provided at this
115 * time for a subset of the packages used by the given [context]. If
116 * information about some of the used packages is not available yet, schedule
117 * its computation, so that it might be available later for other contexts
118 * referencing the same packages.
119 */
120 List<PackageBundle> getLinkedBundles(AnalysisContext context) {
121 Map<String, PackageBundle> unlinkedBundles = getUnlinkedBundles(context);
122 // TODO(scheglov) actually compute available linked bundles
123 return <PackageBundle>[];
124 }
125
126 /**
127 * Return all available unlinked [PackageBundle]s for the given [context],
128 * maybe an empty list, but not `null`.
129 */
130 Map<String, PackageBundle> getUnlinkedBundles(AnalysisContext context) {
131 Map<String, PackageBundle> unlinkedBundles =
132 new HashMap<String, PackageBundle>();
133 Map<String, List<Folder>> packageMap = context.sourceFactory.packageMap;
134 if (packageMap != null) {
135 packageMap.forEach((String packageName, List<Folder> libFolders) {
136 if (libFolders.length == 1) {
137 Folder libFolder = libFolders.first;
138 if (isPathInPubCache(pathContext, libFolder.path)) {
139 PubPackage package = new PubPackage(packageName, libFolder);
140 PackageBundle unlinkedBundle = _getUnlinkedOrSchedule(package);
141 if (unlinkedBundle != null) {
142 unlinkedBundles[packageName] = unlinkedBundle;
143 }
144 }
145 }
146 });
147 }
148 return unlinkedBundles;
149 }
150
151 /**
152 * Compute unlinked bundle for a package from [packagesToComputeUnlinked],
153 * and schedule delayed computation for the next package, if any.
154 */
155 void _computeNextUnlinked() {
156 if (packagesToComputeUnlinked.isNotEmpty) {
157 PubPackage package = packagesToComputeUnlinked.first;
158 _computeUnlinked(package);
159 packagesToComputeUnlinked.remove(package);
160 _scheduleNextUnlinked();
161 } else {
162 if (_onUnlinkedCompleteCompleter != null) {
163 _onUnlinkedCompleteCompleter.complete(true);
164 _onUnlinkedCompleteCompleter = null;
165 }
166 }
167 }
168
169 /**
170 * Compute the unlinked bundle for the package with the given path, put
171 * it in the [unlinkedBundleMap] and store into the [resourceProvider].
172 *
173 * TODO(scheglov) Consider moving into separate isolate(s).
174 */
175 void _computeUnlinked(PubPackage package) {
176 Folder libFolder = package.libFolder;
177 String libPath = libFolder.path + pathContext.separator;
178 PackageBundleAssembler assembler = new PackageBundleAssembler();
179
180 /**
181 * Return the `package` [Uri] for the given [path] in the `lib` folder
182 * of the current package.
183 */
184 Uri getUri(String path) {
185 String pathInLib = path.substring(libPath.length);
186 String uriPath = pathos.posix.joinAll(pathContext.split(pathInLib));
187 String uriStr = 'package:${package.name}/$uriPath';
188 return FastUri.parse(uriStr);
189 }
190
191 /**
192 * If the given [file] is a Dart file, add its unlinked unit.
193 */
194 void addDartFile(File file) {
195 String path = file.path;
196 if (AnalysisEngine.isDartFileName(path)) {
197 Uri uri = getUri(path);
198 Source source = file.createSource(uri);
199 CompilationUnit unit = _parse(source);
200 UnlinkedUnitBuilder unlinkedUnit =
201 serializeAstUnlinked(unit, unit.lineInfo.lineStarts);
202 assembler.addUnlinkedUnit(source, unlinkedUnit);
203 }
204 }
205
206 /**
207 * Visit the [folder] recursively.
208 */
209 void addDartFiles(Folder folder) {
210 List<Resource> children = folder.getChildren();
211 for (Resource child in children) {
212 if (child is File) {
213 addDartFile(child);
214 }
215 }
216 for (Resource child in children) {
217 if (child is Folder) {
218 addDartFiles(child);
219 }
220 }
221 }
222
223 try {
224 addDartFiles(libFolder);
225 List<int> bytes = assembler.assemble().toBuffer();
226 _writeAtomic(package.folder, UNLINKED_BUNDLE_FILE_NAME, bytes);
227 } on FileSystemException {
228 // Ignore file system exceptions.
229 }
230 }
231
232 /**
233 * Return the unlinked [PackageBundle] for the given [package]. If the bundle
234 * has not been compute yet, return `null` and schedule its computation.
235 */
236 PackageBundle _getUnlinkedOrSchedule(PubPackage package) {
237 // Try to find in the cache.
238 PackageBundle bundle = unlinkedBundleMap[package];
239 if (bundle != null) {
240 return bundle;
241 }
242 // Try to read from the file system.
243 File unlinkedFile =
244 package.folder.getChildAssumingFile(UNLINKED_BUNDLE_FILE_NAME);
245 if (unlinkedFile.exists) {
246 try {
247 List<int> bytes = unlinkedFile.readAsBytesSync();
248 bundle = new PackageBundle.fromBuffer(bytes);
249 unlinkedBundleMap[package] = bundle;
250 return bundle;
251 } on FileSystemException {
252 // Ignore file system exceptions.
253 }
254 }
255 // Schedule computation in the background.
256 if (package != null && seenPackages.add(package)) {
257 if (packagesToComputeUnlinked.isEmpty) {
258 _scheduleNextUnlinked();
259 }
260 packagesToComputeUnlinked.add(package);
261 }
262 // The bundle is for available.
263 return null;
264 }
265
266 /**
267 * Parse the given [source] into AST.
268 */
269 CompilationUnit _parse(Source source) {
270 String code = source.contents.data;
271 AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER;
Brian Wilkerson 2016/08/09 14:09:00 Do we want to check that there are no parse errors
scheglov 2016/08/09 14:23:08 I don't think so. The client would see the same AS
272 CharSequenceReader reader = new CharSequenceReader(code);
273 Scanner scanner = new Scanner(source, reader, errorListener);
274 Token token = scanner.tokenize();
275 LineInfo lineInfo = new LineInfo(scanner.lineStarts);
276 Parser parser = new Parser(source, errorListener);
277 CompilationUnit unit = parser.parseCompilationUnit(token);
278 unit.lineInfo = lineInfo;
279 return unit;
280 }
281
282 /**
283 * Schedule delayed computation of the next package unlinked bundle from the
284 * set of [packagesToComputeUnlinked]. We delay each computation because we
285 * want operations in analysis server to proceed, and computing bundles of
286 * packages is a background task.
287 */
288 void _scheduleNextUnlinked() {
289 new Future.delayed(new Duration(milliseconds: 10), _computeNextUnlinked);
290 }
291
292 /**
293 * Atomically write the given [bytes] into the file in the [folder].
294 */
295 void _writeAtomic(Folder folder, String fileName, List<int> bytes) {
296 String filePath = folder.getChildAssumingFile(fileName).path;
297 File tempFile = folder.getChildAssumingFile(tempFileName);
298 tempFile.writeAsBytesSync(bytes);
299 tempFile.renameSync(filePath);
300 }
301
302 /**
303 * Return `true` if the given absolute [path] is in the pub cache.
304 */
305 static bool isPathInPubCache(pathos.Context pathContext, String path) {
306 List<String> parts = pathContext.split(path);
307 for (int i = 0; i < parts.length - 1; i++) {
308 if (parts[i] == '.pub-cache') {
309 return true;
310 }
311 if (parts[i] == 'Pub' && parts[i + 1] == 'Cache') {
312 return true;
313 }
314 }
315 return false;
316 }
317 }
OLDNEW
« no previous file with comments | « pkg/analysis_server/lib/src/analysis_server.dart ('k') | pkg/analyzer/test/src/summary/pub_summary_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698