Chromium Code Reviews| Index: pkg/analysis_server/lib/src/pub_summary.dart |
| diff --git a/pkg/analysis_server/lib/src/pub_summary.dart b/pkg/analysis_server/lib/src/pub_summary.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..479cdc2b156d2fa3d0d8add1d3e9c62336c57acb |
| --- /dev/null |
| +++ b/pkg/analysis_server/lib/src/pub_summary.dart |
| @@ -0,0 +1,274 @@ |
| +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +import 'dart:async'; |
| +import 'dart:core' hide Resource; |
| + |
| +import 'package:analysis_server/src/analysis_server.dart'; |
| +import 'package:analysis_server/src/context_manager.dart'; |
| +import 'package:analyzer/dart/ast/ast.dart'; |
| +import 'package:analyzer/file_system/file_system.dart'; |
| +import 'package:analyzer/src/generated/engine.dart'; |
| +import 'package:analyzer/src/generated/source.dart'; |
| +import 'package:analyzer/src/summary/format.dart'; |
| +import 'package:analyzer/src/summary/summarize_ast.dart' |
| + show serializeAstUnlinked; |
| +import 'package:analyzer/src/summary/summarize_elements.dart' |
| + show PackageBundleAssembler; |
| +import 'package:analyzer/src/task/dart.dart'; |
| +import 'package:analyzer/src/util/fast_uri.dart'; |
| +import 'package:analyzer/task/dart.dart'; |
| +import 'package:analyzer/task/model.dart'; |
| +import 'package:path/src/context.dart' as pathos; |
| + |
| +const visibleForTesting = const Object(); |
| + |
| +/** |
| + * A package in the pub cache. |
| + */ |
| +@visibleForTesting |
|
Brian Wilkerson
2016/08/05 22:09:14
nit: There have been requests for this annotation;
|
| +class PubPackage { |
| + final String name; |
| + final Folder libFolder; |
| + |
| + PubPackage(this.name, this.libFolder); |
| + |
| + Folder get folder => libFolder.parent; |
| + |
| + @override |
| + int get hashCode => libFolder.hashCode; |
| + |
| + @override |
| + bool operator ==(other) { |
| + return other is PubPackage && other.libFolder == libFolder; |
| + } |
| + |
| + @override |
| + String toString() => '($name in $folder)'; |
| +} |
| + |
| +/** |
| + * Class the manages summaries for pub packages. |
|
Paul Berry
2016/08/05 21:43:02
s/the/that/
scheglov
2016/08/05 21:47:41
Done.
|
| + */ |
| +class PubSummaryManager { |
|
Brian Wilkerson
2016/08/05 22:09:14
I'd love to see an example of how clients are expe
|
| + final ResourceProvider resourceProvider; |
| + final AnalysisServer server; |
| + |
| +// /** |
| +// * The map from absolute paths of pub packages in the pub cache to their |
| +// * unlinked summary bundles. |
| +// */ |
| +// final Map<String, PackageBundle> unlinkedBundleMap = |
| +// new HashMap<String, PackageBundle>(); |
| + |
| + /** |
| + * The set of packages to compute summaries for. |
| + */ |
| + final Set<PubPackage> packagesToSummarize = new Set<PubPackage>(); |
| + |
| + /** |
| + * The set of already processed packages, which we have already checked |
| + * for their unlinked summary existence, or scheduled its computing. |
| + */ |
| + final Set<PubPackage> seenPackages = new Set<PubPackage>(); |
| + |
| + /** |
| + * The [Completer] that completes when analysis is complete. |
| + */ |
| + Completer _onCompleteCompleter; |
| + |
| + /** |
| + * Create a new instance and start listening for [AnalysisServer] and |
| + * [AnalysisContext] events, and schedule creating pub summaries. |
| + */ |
| + PubSummaryManager(this.resourceProvider, this.server) { |
| + server.onContextsChanged.listen((ContextsChangedEvent event) { |
| + for (AnalysisContext context in event.added) { |
| + context |
| + .onResultChanged(LIBRARY_ELEMENT1) |
| + .listen(handleNewLibraryElementEvent); |
| + } |
| + }); |
| + } |
| + |
| + /** |
| + * The [Future] that completes when computing of all package summaries is |
| + * complete. |
| + */ |
| + Future get onComplete { |
| + if (packagesToSummarize.isEmpty) { |
| + return new Future.value(); |
| + } |
| + _onCompleteCompleter ??= new Completer(); |
| + return _onCompleteCompleter.future; |
| + } |
| + |
| + /** |
| + * Return the [pathos.Context] corresponding to the [resourceProvider]. |
| + */ |
| + pathos.Context get pathContext => resourceProvider.pathContext; |
| + |
| + /** |
| + * If the given [source] has the 'package' scheme, and its path is in the |
| + * pub cache, return information about the package that contains the [source]. |
| + * Otherwise return `null`. |
| + */ |
| + @visibleForTesting |
| + PubPackage getPackageInPubCache(AnalysisContext context, Source source) { |
| + if (source.uri.scheme == 'package') { |
| + String path = source.fullName; |
| + if (isPathInPubCache(pathContext, path)) { |
| + String packageName = getPackageName(source.uri); |
| + if (packageName != null) { |
| + List<Folder> libFolders = |
| + context.sourceFactory.packageMap[packageName]; |
| + if (libFolders != null && libFolders.length == 1) { |
| + return new PubPackage(packageName, libFolders.first); |
| + } |
| + } |
| + } |
| + } |
| + return null; |
| + } |
| + |
| + /** |
| + * Handle [ResultChangedEvent] for [LIBRARY_ELEMENT1] and schedule computing |
| + * summary for the library, if it is not ready yet. |
| + */ |
| + @visibleForTesting |
| + void handleNewLibraryElementEvent(ResultChangedEvent event) { |
| + AnalysisTarget source = event.target; |
| + if (event.wasComputed && source is Source) { |
| + PubPackage package = getPackageInPubCache(event.context, source); |
| + if (package != null && seenPackages.add(package)) { |
| + packagesToSummarize.add(package); |
| + if (packagesToSummarize.length == 1) { |
| + _scheduleNextPackageSummary(); |
|
Brian Wilkerson
2016/08/05 22:09:13
If a package 'a' depends on a package 'b', do we n
Paul Berry
2016/08/05 22:29:28
That is not a constraint when building unlinked su
|
| + } |
| + } |
| + } |
| + } |
| + |
| + /** |
| + * Compute summary for a package from [packagesToSummarize], and schedule |
| + * delayed computation of the next package summary, if any. |
| + */ |
| + void _computeNextPackageSummary() { |
| + if (packagesToSummarize.isNotEmpty) { |
| + PubPackage package = packagesToSummarize.first; |
| + _computeUnlinkedPackageSummary(package); |
| + packagesToSummarize.remove(package); |
| + _scheduleNextPackageSummary(); |
| + } else { |
| + if (_onCompleteCompleter != null) { |
| + _onCompleteCompleter.complete(true); |
| + _onCompleteCompleter = null; |
| + } |
| + } |
| + } |
| + |
| + /** |
| + * Compute the unlinked summary for the package with the given path, put |
| + * it in the [unlinkedBundleMap] and store into the [resourceProvider]. |
| + * |
| + * TODO(scheglov) Consider moving into separate isolate(s). |
| + */ |
| + void _computeUnlinkedPackageSummary(PubPackage package) { |
| + AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); |
| + context.sourceFactory = new SourceFactory( |
| + <UriResolver>[new DartUriResolver(server.sdkManager.anySdk)]); |
|
Brian Wilkerson
2016/08/05 22:09:14
This won't let us distinguish between packages bui
Paul Berry
2016/08/05 22:29:28
I'm not aware of any constraints Flutter imposes t
|
| + |
| + Folder libFolder = package.libFolder; |
| + String libPath = libFolder.path + pathContext.separator; |
| + PackageBundleAssembler assembler = new PackageBundleAssembler(); |
| + |
| + /** |
| + * If the given [file] is a Dart file, the unlinked summary of it. |
| + */ |
| + void addDartFile(File file) { |
| + String path = file.path; |
| + if (AnalysisEngine.isDartFileName(path)) { |
| + String pathInLib = path.substring(libPath.length); |
| + String uriStr = 'package:${package.name}/$pathInLib'; |
|
Paul Berry
2016/08/05 21:43:02
This looks incorrect for Windows, since on Windows
scheglov
2016/08/05 21:47:41
Fixed.
|
| + Uri uri = FastUri.parse(uriStr); |
| + Source source = file.createSource(uri); |
| + CompilationUnit unit = context.computeResult(source, PARSED_UNIT); |
| + UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(unit); |
| + assembler.addUnlinkedUnit(source, unlinkedUnit); |
| + } |
| + } |
| + |
| + /** |
| + * Visit the [folder] recursively. |
| + */ |
| + void addDartFiles(Folder folder) { |
| + List<Resource> children = folder.getChildren(); |
| + for (Resource child in children) { |
| + if (child is File) { |
| + addDartFile(child); |
| + } |
| + } |
| + for (Resource child in children) { |
|
Brian Wilkerson
2016/08/05 22:09:14
Does the order in which we add the children matter
|
| + if (child is Folder) { |
| + addDartFiles(child); |
| + } |
| + } |
| + } |
| + |
| + try { |
| + addDartFiles(libFolder); |
| + List<int> bytes = assembler.assemble().toBuffer(); |
| + package.folder |
| + .getChildAssumingFile('summary_spec.full.ds') |
| + .writeAsBytesSync(bytes); |
| + } on FileSystemException { |
| + // Ignore file system exceptions. |
| + } |
| + } |
| + |
| + /** |
| + * Schedule delayed computation of the next package summary from the set of |
| + * [packagesToSummarize]. We delay each computation because we want |
| + * operations in analysis server to proceed, and computing summaries of |
| + * packages is a background task. |
| + */ |
| + void _scheduleNextPackageSummary() { |
| + new Future.delayed( |
| + new Duration(milliseconds: 10), _computeNextPackageSummary); |
| + } |
| + |
| + /** |
| + * If the given [uri] has the `package` scheme, return the names of the |
|
Paul Berry
2016/08/05 21:43:02
s/names/name/
scheglov
2016/08/05 21:47:41
Done.
|
| + * package that contains the referenced resource. Otherwise return `null`. |
| + * |
| + * For example `package:foo/bar.dart` => `foo`. |
| + */ |
| + static String getPackageName(Uri uri) { |
| + const String PACKAGE_SCHEME = 'package:'; |
| + String text = uri.toString(); |
| + if (text.startsWith(PACKAGE_SCHEME)) { |
| + int index = text.indexOf('/'); |
| + if (index != -1) { |
| + return text.substring(PACKAGE_SCHEME.length, index); |
| + } |
| + } |
| + return null; |
| + } |
| + |
| + /** |
| + * Return `true` if the given absolute [path] is in the pub cache. |
| + */ |
| + static bool isPathInPubCache(pathos.Context pathContext, String path) { |
| + List<String> parts = pathContext.split(path); |
| + for (int i = 0; i < parts.length - 1; i++) { |
| + if (parts[i] == '.pub-cache') { |
| + return true; |
| + } |
| + if (parts[i] == 'Pub' && parts[i + 1] == 'Cache') { |
| + return true; |
| + } |
| + } |
| + return false; |
| + } |
| +} |