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

Unified 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: Rework to better fit actual use. 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 side-by-side diff with in-line comments
Download patch
Index: pkg/analyzer/lib/src/summary/pub_summary.dart
diff --git a/pkg/analyzer/lib/src/summary/pub_summary.dart b/pkg/analyzer/lib/src/summary/pub_summary.dart
new file mode 100644
index 0000000000000000000000000000000000000000..b9271ea4976bf5ca45e7367066ec488778fa9be0
--- /dev/null
+++ b/pkg/analyzer/lib/src/summary/pub_summary.dart
@@ -0,0 +1,286 @@
+// 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:collection';
+import 'dart:core' hide Resource;
+
+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/sdk.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/summary/format.dart';
+import 'package:analyzer/src/summary/idl.dart';
+import 'package:analyzer/src/summary/package_bundle_reader.dart'
+ show ResynthesizerResultProvider;
+import 'package:analyzer/src/summary/summarize_ast.dart'
+ show serializeAstUnlinked;
+import 'package:analyzer/src/summary/summarize_elements.dart'
+ show PackageBundleAssembler;
+import 'package:analyzer/src/util/fast_uri.dart';
+import 'package:analyzer/task/dart.dart';
+import 'package:path/path.dart' as pathos;
+
+/**
+ * A package in the pub cache.
+ */
+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 that manages summaries for pub packages.
+ *
+ * The client should call [getLinkedBundles] after creating a new
+ * [AnalysisContext] and configuring its source factory, but before computing
+ * any analysis results. The returned linked bundles can be used to create and
+ * configure [ResynthesizerResultProvider] for the context.
+ */
+class PubSummaryManager {
+ static const UNLINKED_BUNDLE_FILE_NAME = 'unlinked.ds';
+
+ final ResourceProvider resourceProvider;
+
+ /**
+ * A [DartSdk]. While we create only unlinked summaries, we just need it to
+ * be able to resolve `dart:core` (parse task requires this).
+ */
+ final DartSdk defaultDartSdk;
+
+ /**
+ * The map from [PubPackage]s to their unlinked [PackageBundle]s in the pub
+ * cache.
+ */
+ final Map<PubPackage, PackageBundle> unlinkedBundleMap =
+ new HashMap<PubPackage, PackageBundle>();
+
+ /**
+ * The set of packages to compute unlinked summaries for.
+ */
+ final Set<PubPackage> packagesToComputeUnlinked = new Set<PubPackage>();
+
+ /**
+ * The set of already processed packages, which we have already checked
+ * for their unlinked bundle existence, or scheduled its computing.
+ */
+ final Set<PubPackage> seenPackages = new Set<PubPackage>();
+
+ /**
+ * The [Completer] that completes when computing of all scheduled unlinked
+ * bundles is complete.
+ */
+ Completer _onUnlinkedCompleteCompleter;
+
+ PubSummaryManager(this.resourceProvider, this.defaultDartSdk);
+
+ /**
+ * The [Future] that completes when computing of all scheduled unlinked
+ * bundles is complete.
+ */
+ Future get onUnlinkedComplete {
+ if (packagesToComputeUnlinked.isEmpty) {
+ return new Future.value();
+ }
+ _onUnlinkedCompleteCompleter ??= new Completer();
+ return _onUnlinkedCompleteCompleter.future;
+ }
+
+ /**
+ * Return the [pathos.Context] corresponding to the [resourceProvider].
+ */
+ pathos.Context get pathContext => resourceProvider.pathContext;
+
+ /**
+ * Return the list of linked [PackageBundle]s that can be provided at this
+ * time for a subset of the packages used by the given [context]. If
+ * information about some of the used packages is not available yet, schedule
+ * its computation, so that it might be available later for other contexts
+ * referencing the same packages.
+ */
+ List<PackageBundle> getLinkedBundles(AnalysisContext context) {
Paul Berry 2016/08/08 12:24:59 Sorry for not picking up on this during the last r
scheglov 2016/08/09 03:41:07 Yes, I plan to perform profiling on some dependenc
+ Map<String, PackageBundle> unlinkedBundles = getUnlinkedBundles(context);
+ // TODO(scheglov) actually compute available linked bundles
+ return <PackageBundle>[];
+ }
+
+ /**
+ * Return all available unlinked [PackageBundle]s for the given [context],
+ * maybe an empty list, but not `null`.
+ */
+ Map<String, PackageBundle> getUnlinkedBundles(AnalysisContext context) {
Paul Berry 2016/08/08 12:24:59 Similar concern here; also ok if you want to defer
+ Map<String, PackageBundle> unlinkedBundles =
+ new HashMap<String, PackageBundle>();
+ Map<String, List<Folder>> packageMap = context.sourceFactory.packageMap;
+ if (packageMap != null) {
+ packageMap.forEach((String packageName, List<Folder> libFolders) {
+ if (libFolders.length == 1) {
+ Folder libFolder = libFolders.first;
+ if (isPathInPubCache(pathContext, libFolder.path)) {
+ PubPackage package = new PubPackage(packageName, libFolder);
+ PackageBundle unlinkedBundle = _getUnlinkedOrSchedule(package);
+ if (unlinkedBundle != null) {
+ unlinkedBundles[packageName] = unlinkedBundle;
+ }
+ }
+ }
+ });
+ }
+ return unlinkedBundles;
+ }
+
+ /**
+ * Compute unlinked bundle for a package from [packagesToComputeUnlinked],
+ * and schedule delayed computation for the next package, if any.
+ */
+ void _computeNextUnlinked() {
+ if (packagesToComputeUnlinked.isNotEmpty) {
+ PubPackage package = packagesToComputeUnlinked.first;
+ _computeUnlinked(package);
+ packagesToComputeUnlinked.remove(package);
+ _scheduleNextUnlinked();
+ } else {
+ if (_onUnlinkedCompleteCompleter != null) {
+ _onUnlinkedCompleteCompleter.complete(true);
+ _onUnlinkedCompleteCompleter = null;
+ }
+ }
+ }
+
+ /**
+ * Compute the unlinked bundle 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 _computeUnlinked(PubPackage package) {
+ AnalysisContext context = AnalysisEngine.instance.createAnalysisContext();
+ context.sourceFactory =
+ new SourceFactory(<UriResolver>[new DartUriResolver(defaultDartSdk)]);
+
+ Folder libFolder = package.libFolder;
+ String libPath = libFolder.path + pathContext.separator;
+ PackageBundleAssembler assembler = new PackageBundleAssembler();
+
+ /**
+ * If the given [file] is a Dart file, add its unlinked unit.
+ */
+ void addDartFile(File file) {
+ String path = file.path;
+ if (AnalysisEngine.isDartFileName(path)) {
+ String pathInLib = path.substring(libPath.length);
+ String uriPath = pathos.posix.joinAll(pathContext.split(pathInLib));
+ String uriStr = 'package:${package.name}/$uriPath';
+ Uri uri = FastUri.parse(uriStr);
+ Source source = file.createSource(uri);
+ CompilationUnit unit = context.computeResult(source, PARSED_UNIT);
Brian Wilkerson 2016/08/08 14:32:55 Do we want to discard all of the data computed whi
+ 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) {
+ if (child is Folder) {
+ addDartFiles(child);
+ }
+ }
+ }
+
+ try {
+ addDartFiles(libFolder);
+ List<int> bytes = assembler.assemble().toBuffer();
+ package.folder
+ .getChildAssumingFile(UNLINKED_BUNDLE_FILE_NAME)
+ .writeAsBytesSync(bytes);
+ } on FileSystemException {
+ // Ignore file system exceptions.
+ }
+ }
+
+ /**
+ * Return the unlinked [PackageBundle] for the given [package]. If the bundle
+ * has not been compute yet, return `null` and schedule its computation.
+ */
+ PackageBundle _getUnlinkedOrSchedule(PubPackage package) {
+ // Try to find in the cache.
+ PackageBundle bundle = unlinkedBundleMap[package];
+ if (bundle != null) {
+ return bundle;
+ }
+ // Try to read from the file system.
+ File unlinkedFile =
+ package.folder.getChildAssumingFile(UNLINKED_BUNDLE_FILE_NAME);
+ if (unlinkedFile.exists) {
+ try {
+ List<int> bytes = unlinkedFile.readAsBytesSync();
+ bundle = new PackageBundle.fromBuffer(bytes);
+ unlinkedBundleMap[package] = bundle;
+ return bundle;
+ } on FileSystemException {
+ // Ignore file system exceptions.
+ }
+ }
+ // Schedule computation in the background.
+ if (package != null && seenPackages.add(package)) {
+ packagesToComputeUnlinked.add(package);
+ if (packagesToComputeUnlinked.length == 1) {
Brian Wilkerson 2016/08/08 14:32:56 Given that `packagesToComputeUnlinked` is a set, I
scheglov 2016/08/09 03:41:07 Fixed.
+ _scheduleNextUnlinked();
+ }
+ }
+ // The bundle is for available.
+ return null;
+ }
+
+ /**
+ * Schedule delayed computation of the next package unlinked bundle from the
+ * set of [packagesToComputeUnlinked]. We delay each computation because we
+ * want operations in analysis server to proceed, and computing bundles of
+ * packages is a background task.
+ */
+ void _scheduleNextUnlinked() {
+ new Future.delayed(new Duration(milliseconds: 10), _computeNextUnlinked);
Brian Wilkerson 2016/08/08 14:32:55 It seems odd to me that we're scheduling work here
+ }
+
+ /**
+ * 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;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698