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

Unified Diff: packages/analyzer/lib/src/summary/bazel_summary.dart

Issue 2990843002: Removed fixed dependencies (Closed)
Patch Set: Created 3 years, 5 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
« no previous file with comments | « packages/analyzer/lib/src/summary/base.dart ('k') | packages/analyzer/lib/src/summary/flat_buffers.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: packages/analyzer/lib/src/summary/bazel_summary.dart
diff --git a/packages/analyzer/lib/src/summary/bazel_summary.dart b/packages/analyzer/lib/src/summary/bazel_summary.dart
new file mode 100644
index 0000000000000000000000000000000000000000..a7b9b880c62664c66879d09b9f1400bcd0a3a68c
--- /dev/null
+++ b/packages/analyzer/lib/src/summary/bazel_summary.dart
@@ -0,0 +1,561 @@
+// 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:convert';
+
+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/generated/utilities_collection.dart';
+import 'package:analyzer/src/summary/api_signature.dart';
+import 'package:analyzer/src/summary/format.dart';
+import 'package:analyzer/src/summary/idl.dart';
+import 'package:analyzer/src/summary/link.dart';
+import 'package:analyzer/src/summary/package_bundle_reader.dart';
+import 'package:analyzer/src/summary/summarize_elements.dart';
+import 'package:analyzer/src/util/fast_uri.dart';
+import 'package:convert/convert.dart';
+import 'package:crypto/crypto.dart';
+import 'package:meta/meta.dart';
+
+/**
+ * Return the [Folder] where bundles for the given [absoluteUri] should be
+ * looked for. This folder should contain corresponding `*.full.ds` files,
+ * possibly more than one one. Return `null` if the given [absoluteUri]
+ * does not have the expected structure, so the output path cannot be computed.
+ */
+typedef Folder GetOutputFolder(Uri absoluteUri);
+
+/**
+ * Load linked packages on demand from [SummaryProvider].
+ */
+class BazelResultProvider extends ResynthesizerResultProvider {
+ final SummaryDataStore dataStore;
+ final SummaryProvider summaryProvider;
+
+ final Map<Source, bool> sourceToSuccessMap = <Source, bool>{};
+ final Set<Package> addedPackages = new Set<Package>();
+
+ factory BazelResultProvider(SummaryProvider summaryProvider) {
+ SummaryDataStore dataStore = new SummaryDataStore(const <String>[]);
+ return new BazelResultProvider._(dataStore, summaryProvider);
+ }
+
+ BazelResultProvider._(
+ SummaryDataStore dataStore, SummaryProvider summaryProvider)
+ : dataStore = dataStore,
+ summaryProvider = summaryProvider,
+ super(summaryProvider.context, dataStore) {
+ AnalysisContext sdkContext = context.sourceFactory.dartSdk.context;
+ createResynthesizer(sdkContext, sdkContext.typeProvider);
+ }
+
+ @override
+ bool hasResultsForSource(Source source) {
+ return sourceToSuccessMap.putIfAbsent(source, () {
+ List<Package> packages = summaryProvider.getLinkedPackages(source);
+ if (packages == null) {
+ return false;
+ }
+ for (Package package in packages) {
+ if (addedPackages.add(package)) {
+ dataStore.addBundle(null, package.unlinked);
+ dataStore.addBundle(null, package.linked);
+ }
+ }
+ String uriString = source.uri.toString();
+ return resynthesizer.hasLibrarySummary(uriString);
+ });
+ }
+}
+
+/**
+ * Information about a Dart package in Bazel.
+ */
+class Package {
+ final File unlinkedFile;
+ final PackageBundle unlinked;
+ final Set<String> _unitUris = new Set<String>();
+
+ PackageBundle _linked;
+
+ Package(this.unlinkedFile, this.unlinked) {
+ _unitUris.addAll(unlinked.unlinkedUnitUris);
+ }
+
+ PackageBundle get linked => _linked;
+
+ @override
+ String toString() => '$unlinkedFile';
+}
+
+/**
+ * Class that reads summaries of Bazel packages.
+ *
+ * When the client needs to produce a resolution result for a new [Source], it
+ * should call [getLinkedPackages] to check whether there is the set of
+ * packages to resynthesize resolution results.
+ */
+class SummaryProvider {
+ final ResourceProvider provider;
+ final String tempFileName;
+ final GetOutputFolder getOutputFolder;
+ final Folder linkedCacheFolder;
+ final AnalysisContext context;
+ final PackageBundle sdkBundle;
+
+ /**
+ * If `true` (by default), then linking new bundles is allowed.
+ * Otherwise only using existing cached bundles can be used.
+ */
+ final bool allowLinking;
+
+ /**
+ * See [PackageBundleAssembler.currentMajorVersion].
+ */
+ final int majorVersion;
+
+ /**
+ * Mapping from bundle paths to corresponding [Package]s. The packages in
+ * the map were consistent with their constituent sources at the moment when
+ * they were put into the map.
+ */
+ final Map<Folder, List<Package>> folderToPackagesMap = {};
+
+ /**
+ * Mapping from [Uri]s to corresponding [Package]s.
+ */
+ final Map<Uri, Package> uriToPackageMap = {};
+
+ /**
+ * Mapping from [Package]s to corresponding [_LinkNode]s.
+ */
+ final Map<Package, _LinkNode> packageToNodeMap = {};
+
+ SummaryProvider(
+ this.provider,
+ this.tempFileName,
+ this.getOutputFolder,
+ this.linkedCacheFolder,
+ AnalysisContext context,
+ {@visibleForTesting
+ this.allowLinking: true,
+ @visibleForTesting
+ this.majorVersion: PackageBundleAssembler.currentMajorVersion})
+ : context = context,
+ sdkBundle = context.sourceFactory.dartSdk?.getLinkedBundle();
+
+ /**
+ * Return the complete list of [Package]s that are required to provide all
+ * resolution results for the given [source].
+ *
+ * The same list of packages is returned for the same [Source], i.e. always
+ * the full list, not a difference with a previous request. It is up to the
+ * client to decide whether some of the returned packages should be excluded
+ * as already mixed into a resynthesizer.
+ *
+ * If the full set of packages cannot be produced, for example because some
+ * bundles are not built, or out of date, etc, then `null` is returned.
+ */
+ List<Package> getLinkedPackages(Source source) {
+ // Find the node that contains the source.
+ _LinkNode node = _getLinkNodeForUri(source.uri);
+ if (node == null) {
+ return null;
+ }
+
+ // Compute all transitive dependencies.
+ node.computeTransitiveDependencies();
+ List<_LinkNode> nodes = node.transitiveDependencies.toList();
+ nodes.forEach((dependency) => dependency.computeTransitiveDependencies());
+
+ // Fail if any dependency cannot be resolved.
+ if (node.failed) {
+ return null;
+ }
+
+ // Read existing cached linked bundles.
+ for (_LinkNode node in nodes) {
+ _readLinked(node);
+ }
+
+ // Link new packages, if allowed.
+ if (allowLinking) {
+ _link(nodes);
+ }
+
+ // Create successfully linked packages.
+ return nodes
+ .map((node) => node.package)
+ .where((package) => package.linked != null)
+ .toList();
+ }
+
+ /**
+ * Return the [Package] that contains information about the source with
+ * the given [uri], or `null` if such package does not exist.
+ */
+ @visibleForTesting
+ Package getUnlinkedForUri(Uri uri) {
+ return uriToPackageMap.putIfAbsent(uri, () {
+ Folder outputFolder = getOutputFolder(uri);
+ if (outputFolder != null) {
+ String uriStr = uri.toString();
+ List<Package> packages = _getUnlinkedPackages(outputFolder);
+ for (Package package in packages) {
+ if (package._unitUris.contains(uriStr)) {
+ return package;
+ }
+ }
+ }
+ return null;
+ });
+ }
+
+ /**
+ * Return the hexadecimal string of the MD5 hash of the contents of the
+ * given [source] in [context].
+ */
+ String _computeSourceHashHex(Source source) {
+ String text = context.getContents(source).data;
+ List<int> bytes = UTF8.encode(text);
+ List<int> hashBytes = md5.convert(bytes).bytes;
+ return hex.encode(hashBytes);
+ }
+
+ /**
+ * Return the name of the file for a linked bundle, in strong or spec mode.
+ */
+ String _getLinkedName(String hash) {
+ if (context.analysisOptions.strongMode) {
+ return 'linked_$hash.ds';
+ } else {
+ return 'linked_spec_$hash.ds';
+ }
+ }
+
+ /**
+ * Return the node for the given [uri], or `null` if there is no unlinked
+ * bundle that contains [uri].
+ */
+ _LinkNode _getLinkNodeForUri(Uri uri) {
+ Package package = getUnlinkedForUri(uri);
+ return packageToNodeMap.putIfAbsent(package, () {
+ if (package == null) {
+ return null;
+ }
+ return new _LinkNode(this, package);
+ });
+ }
+
+ /**
+ * Return all consistent unlinked [Package]s in the given [folder]. Some of
+ * the returned packages might be already linked.
+ */
+ List<Package> _getUnlinkedPackages(Folder folder) {
+ List<Package> packages = folderToPackagesMap[folder];
+ if (packages == null) {
+ packages = <Package>[];
+ try {
+ List<Resource> children = folder.getChildren();
+ for (Resource child in children) {
+ if (child is File) {
+ String packagePath = child.path;
+ if (packagePath.toLowerCase().endsWith('.full.ds')) {
+ Package package = _readUnlinkedPackage(child);
+ if (package != null) {
+ packages.add(package);
+ }
+ }
+ }
+ }
+ } on FileSystemException {}
+ folderToPackagesMap[folder] = packages;
+ }
+ return packages;
+ }
+
+ /**
+ * Return `true` if the unlinked information of the [bundle] is consistent
+ * with its constituent sources in [context].
+ */
+ bool _isUnlinkedBundleConsistent(PackageBundle bundle) {
+ try {
+ // Compute hashes of the constituent sources.
+ List<String> actualHashes = <String>[];
+ for (String uri in bundle.unlinkedUnitUris) {
+ Source source = context.sourceFactory.resolveUri(null, uri);
+ if (source == null) {
+ return false;
+ }
+ String hash = _computeSourceHashHex(source);
+ actualHashes.add(hash);
+ }
+ // Compare sorted actual and bundle unit hashes.
+ List<String> bundleHashes = bundle.unlinkedUnitHashes.toList()..sort();
+ actualHashes.sort();
+ return listsEqual(actualHashes, bundleHashes);
+ } on FileSystemException {}
+ return false;
+ }
+
+ /**
+ * Link the given [nodes].
+ */
+ void _link(List<_LinkNode> nodes) {
+ // Fill the store with bundles.
+ // Append the linked SDK bundle.
+ // Append unlinked and (if read from a cache) linked package bundles.
+ SummaryDataStore store = new SummaryDataStore(const <String>[]);
+ store.addBundle(null, sdkBundle);
+ for (_LinkNode node in nodes) {
+ store.addBundle(null, node.package.unlinked);
+ if (node.package.linked != null) {
+ store.addBundle(null, node.package.linked);
+ }
+ }
+
+ // Prepare URIs to link.
+ Map<String, _LinkNode> uriToNode = <String, _LinkNode>{};
+ for (_LinkNode node in nodes) {
+ if (!node.isReady) {
+ for (String uri in node.package.unlinked.unlinkedUnitUris) {
+ uriToNode[uri] = node;
+ }
+ }
+ }
+ Set<String> libraryUris = uriToNode.keys.toSet();
+
+ // Perform linking.
+ Map<String, LinkedLibraryBuilder> linkedLibraries =
+ link(libraryUris, (String uri) {
+ return store.linkedMap[uri];
+ }, (String uri) {
+ return store.unlinkedMap[uri];
+ }, context.declaredVariables.get, context.analysisOptions.strongMode);
+
+ // Assemble newly linked bundles.
+ for (_LinkNode node in nodes) {
+ if (!node.isReady) {
+ PackageBundleAssembler assembler = new PackageBundleAssembler();
+ linkedLibraries.forEach((uri, linkedLibrary) {
+ if (identical(uriToNode[uri], node)) {
+ assembler.addLinkedLibrary(uri, linkedLibrary);
+ }
+ });
+ List<int> bytes = assembler.assemble().toBuffer();
+ node.package._linked = new PackageBundle.fromBuffer(bytes);
+ _writeLinked(node, bytes);
+ }
+ }
+ }
+
+ /**
+ * Attempt to read the linked bundle that corresponds to the given [node]
+ * with all its transitive dependencies.
+ */
+ void _readLinked(_LinkNode node) {
+ String hash = node.linkedHash;
+ if (!node.isReady && hash != null) {
+ String fileName = _getLinkedName(hash);
+ File file = linkedCacheFolder.getChildAssumingFile(fileName);
+ // Try to read from the file system.
+ if (file.exists) {
+ try {
+ List<int> bytes = file.readAsBytesSync();
+ node.package._linked = new PackageBundle.fromBuffer(bytes);
+ } on FileSystemException {
+ // Ignore file system exceptions.
+ }
+ }
+ }
+ }
+
+ /**
+ * Read the unlinked [Package] from the given [file], or return `null` if the
+ * file does not exist, or it cannot be read, or is not consistent with the
+ * constituent sources on the file system.
+ */
+ Package _readUnlinkedPackage(File file) {
+ try {
+ List<int> bytes = file.readAsBytesSync();
+ PackageBundle bundle = new PackageBundle.fromBuffer(bytes);
+ // Check the major version.
+ if (bundle.majorVersion != majorVersion) {
+ return null;
+ }
+ // Check for consistency, and fail if it's not.
+ if (!_isUnlinkedBundleConsistent(bundle)) {
+ return null;
+ }
+ // OK, use the bundle.
+ return new Package(file, bundle);
+ } on FileSystemException {}
+ return null;
+ }
+
+ /**
+ * Atomically write the given [bytes] into the file in the [folder].
+ */
+ void _writeAtomic(Folder folder, String fileName, List<int> bytes) {
+ String filePath = folder.getChildAssumingFile(fileName).path;
+ File tempFile = folder.getChildAssumingFile(tempFileName);
+ tempFile.writeAsBytesSync(bytes);
+ tempFile.renameSync(filePath);
+ }
+
+ /**
+ * If a new linked bundle was linked for the given [node], write the bundle
+ * into the memory cache and the file system.
+ */
+ void _writeLinked(_LinkNode node, List<int> bytes) {
+ String hash = node.linkedHash;
+ if (hash != null) {
+ String fileName = _getLinkedName(hash);
+ _writeAtomic(linkedCacheFolder, fileName, bytes);
+ }
+ }
+}
+
+/**
+ * Information about a single [Package].
+ */
+class _LinkNode {
+ final SummaryProvider linker;
+ final Package package;
+
+ bool failed = false;
+ Set<_LinkNode> transitiveDependencies;
+
+ List<_LinkNode> _dependencies;
+ String _linkedHash;
+
+ _LinkNode(this.linker, this.package);
+
+ /**
+ * Retrieve the dependencies of this node.
+ */
+ List<_LinkNode> get dependencies {
+ if (_dependencies == null) {
+ Set<_LinkNode> dependencies = new Set<_LinkNode>();
+
+ void appendDependency(String uriStr) {
+ Uri uri = FastUri.parse(uriStr);
+ if (!uri.hasScheme) {
+ // A relative path in this package, skip it.
+ } else if (uri.scheme == 'dart') {
+ // Dependency on the SDK is implicit and always added.
+ // The SDK linked bundle is precomputed before linking packages.
+ } else if (uri.scheme == 'package') {
+ _LinkNode packageNode = linker._getLinkNodeForUri(uri);
+ if (packageNode == null) {
+ failed = true;
+ }
+ if (packageNode != null) {
+ dependencies.add(packageNode);
+ }
+ } else {
+ failed = true;
+ }
+ }
+
+ for (UnlinkedUnit unit in package.unlinked.unlinkedUnits) {
+ for (UnlinkedImport import in unit.imports) {
+ if (!import.isImplicit) {
+ appendDependency(import.uri);
+ }
+ }
+ for (UnlinkedExportPublic export in unit.publicNamespace.exports) {
+ appendDependency(export.uri);
+ }
+ }
+
+ _dependencies = dependencies.toList();
+ }
+ return _dependencies;
+ }
+
+ /**
+ * Return `true` is the node is ready - has the linked bundle or failed (does
+ * not have all required dependencies).
+ */
+ bool get isReady => package.linked != null || failed;
+
+ /**
+ * Return the hash string that corresponds to this linked bundle in the
+ * context of its SDK bundle and transitive dependencies. Return `null` if
+ * the hash computation fails, because for example the full transitive
+ * dependencies cannot computed.
+ */
+ String get linkedHash {
+ if (_linkedHash == null && transitiveDependencies != null && !failed) {
+ ApiSignature signature = new ApiSignature();
+ // Add all unlinked API signatures.
+ List<String> signatures = <String>[];
+ signatures.add(linker.sdkBundle.apiSignature);
+ transitiveDependencies
+ .map((node) => node.package.unlinked.apiSignature)
+ .forEach(signatures.add);
+ signatures.sort();
+ signatures.forEach(signature.addString);
+ // Combine into a single hash.
+ appendDeclaredVariables(signature);
+ _linkedHash = signature.toHex();
+ }
+ return _linkedHash;
+ }
+
+ /**
+ * Append names and values of all referenced declared variables (even the
+ * ones without actually declared values) to the given [signature].
+ */
+ void appendDeclaredVariables(ApiSignature signature) {
+ Set<String> nameSet = new Set<String>();
+ for (_LinkNode node in transitiveDependencies) {
+ for (UnlinkedUnit unit in node.package.unlinked.unlinkedUnits) {
+ for (UnlinkedImport import in unit.imports) {
+ for (UnlinkedConfiguration configuration in import.configurations) {
+ nameSet.add(configuration.name);
+ }
+ }
+ for (UnlinkedExportPublic export in unit.publicNamespace.exports) {
+ for (UnlinkedConfiguration configuration in export.configurations) {
+ nameSet.add(configuration.name);
+ }
+ }
+ }
+ }
+ List<String> sortedNameList = nameSet.toList()..sort();
+ signature.addInt(sortedNameList.length);
+ for (String name in sortedNameList) {
+ signature.addString(name);
+ signature.addString(linker.context.declaredVariables.get(name) ?? '');
+ }
+ }
+
+ /**
+ * Compute the set of existing transitive dependencies for this node.
+ * If any dependency cannot be resolved, then set [failed] to `true`.
+ * Only unlinked bundle is used, so this method can be called before linking.
+ */
+ void computeTransitiveDependencies() {
+ if (transitiveDependencies == null) {
+ transitiveDependencies = new Set<_LinkNode>();
+
+ void appendDependencies(_LinkNode node) {
+ if (transitiveDependencies.add(node)) {
+ node.dependencies.forEach(appendDependencies);
+ }
+ }
+
+ appendDependencies(this);
+ if (transitiveDependencies.any((node) => node.failed)) {
+ failed = true;
+ }
+ }
+ }
+
+ @override
+ String toString() => package.toString();
+}
« no previous file with comments | « packages/analyzer/lib/src/summary/base.dart ('k') | packages/analyzer/lib/src/summary/flat_buffers.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698