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

Unified Diff: packages/analyzer/lib/src/summary/pub_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
Index: packages/analyzer/lib/src/summary/pub_summary.dart
diff --git a/packages/analyzer/lib/src/summary/pub_summary.dart b/packages/analyzer/lib/src/summary/pub_summary.dart
new file mode 100644
index 0000000000000000000000000000000000000000..171c2ff12da4986e4a97479d5eac4bae4ab2d30a
--- /dev/null
+++ b/packages/analyzer/lib/src/summary/pub_summary.dart
@@ -0,0 +1,836 @@
+// 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';
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/dart/scanner/reader.dart';
+import 'package:analyzer/src/dart/scanner/scanner.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/parser.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'
+ show ResynthesizerResultProvider, SummaryDataStore;
+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:convert/convert.dart';
+import 'package:crypto/crypto.dart';
+import 'package:meta/meta.dart';
+import 'package:path/path.dart' as pathos;
+
+/**
+ * Unlinked and linked information about a [PubPackage].
+ */
+class LinkedPubPackage {
+ final PubPackage package;
+ final PackageBundle unlinked;
+ final PackageBundle linked;
+
+ final String linkedHash;
+
+ LinkedPubPackage(this.package, this.unlinked, this.linked, this.linkedHash);
+
+ @override
+ String toString() => package.toString();
+}
+
+/**
+ * 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_NAME = 'unlinked.ds';
+ static const UNLINKED_SPEC_NAME = 'unlinked_spec.ds';
+
+ /**
+ * 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;
+
+ final ResourceProvider resourceProvider;
+
+ /**
+ * The name of the temporary file that is used for atomic writes.
+ */
+ final String tempFileName;
+
+ /**
+ * The map from [PubPackage]s to their unlinked [PackageBundle]s in the pub
+ * cache.
+ */
+ final Map<PubPackage, PackageBundle> unlinkedBundleMap =
+ new HashMap<PubPackage, PackageBundle>();
+
+ /**
+ * The map from linked file paths to the corresponding linked bundles.
+ */
+ final Map<String, PackageBundle> linkedBundleMap =
+ new HashMap<String, 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.tempFileName,
+ {@visibleForTesting this.allowLinking: true,
+ @visibleForTesting this.majorVersion:
+ PackageBundleAssembler.currentMajorVersion});
+
+ /**
+ * 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;
+
+ /**
+ * Complete when the unlinked bundles for the package with the given [name]
+ * and the [libFolder] are computed and written to the files.
+ *
+ * This method is intended to be used for generating unlinked bundles for
+ * the `Flutter` packages.
+ */
+ Future<Null> computeUnlinkedForFolder(String name, Folder libFolder) async {
+ PubPackage package = new PubPackage(name, libFolder);
+ _scheduleUnlinked(package);
+ await onUnlinkedComplete;
+ }
+
+ /**
+ * Return the list of linked [LinkedPubPackage]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<LinkedPubPackage> getLinkedBundles(AnalysisContext context) {
+ return new _ContextLinker(this, context).getLinkedBundles();
+ }
+
+ /**
+ * Return all available unlinked [PackageBundle]s for the given [context],
+ * maybe an empty map, but not `null`.
+ */
+ @visibleForTesting
+ Map<PubPackage, PackageBundle> getUnlinkedBundles(AnalysisContext context) {
+ bool strong = context.analysisOptions.strongMode;
+ Map<PubPackage, PackageBundle> unlinkedBundles =
+ new HashMap<PubPackage, 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;
+ PubPackage package = new PubPackage(packageName, libFolder);
+ PackageBundle unlinkedBundle =
+ _getUnlinkedOrSchedule(package, strong);
+ if (unlinkedBundle != null) {
+ unlinkedBundles[package] = 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, false);
+ _computeUnlinked(package, true);
+ 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, bool strong) {
+ Folder libFolder = package.libFolder;
+ String libPath = libFolder.path + pathContext.separator;
+ PackageBundleAssembler assembler = new PackageBundleAssembler();
+
+ /**
+ * Return the `package` [Uri] for the given [path] in the `lib` folder
+ * of the current package.
+ */
+ Uri getUri(String path) {
+ String pathInLib = path.substring(libPath.length);
+ String uriPath = pathos.posix.joinAll(pathContext.split(pathInLib));
+ String uriStr = 'package:${package.name}/$uriPath';
+ return FastUri.parse(uriStr);
+ }
+
+ /**
+ * If the given [file] is a Dart file, add its unlinked unit.
+ */
+ void addDartFile(File file) {
+ String path = file.path;
+ if (AnalysisEngine.isDartFileName(path)) {
+ Uri uri = getUri(path);
+ Source source = file.createSource(uri);
+ CompilationUnit unit = _parse(source, strong);
+ 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);
+ PackageBundleBuilder bundleWriter = assembler.assemble();
+ bundleWriter.majorVersion = majorVersion;
+ List<int> bytes = bundleWriter.toBuffer();
+ String fileName = _getUnlinkedName(strong);
+ _writeAtomic(package.folder, fileName, bytes);
+ } on FileSystemException {
+ // Ignore file system exceptions.
+ }
+ }
+
+ /**
+ * Return the name of the file for an unlinked bundle, in strong or spec mode.
+ */
+ String _getUnlinkedName(bool strong) {
+ if (strong) {
+ return UNLINKED_NAME;
+ } else {
+ return UNLINKED_SPEC_NAME;
+ }
+ }
+
+ /**
+ * 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, bool strong) {
+ // Try to find in the cache.
+ PackageBundle bundle = unlinkedBundleMap[package];
+ if (bundle != null) {
+ return bundle;
+ }
+
+ // Try to read from the file system.
+ String fileName = _getUnlinkedName(strong);
+ File file = package.folder.getChildAssumingFile(fileName);
+ if (file.exists) {
+ try {
+ List<int> bytes = file.readAsBytesSync();
+ bundle = new PackageBundle.fromBuffer(bytes);
+ } on FileSystemException {
+ // Ignore file system exceptions.
+ }
+ }
+
+ // Verify compatibility and consistency.
+ bool isInPubCache = isPathInPubCache(pathContext, package.folder.path);
+ if (bundle != null &&
+ bundle.majorVersion == majorVersion &&
+ (isInPubCache || _isConsistent(package, bundle))) {
+ unlinkedBundleMap[package] = bundle;
+ return bundle;
+ }
+
+ // Schedule computation in the background, if in the pub cache.
+ if (isInPubCache) {
+ if (seenPackages.add(package)) {
+ _scheduleUnlinked(package);
+ }
+ }
+
+ // The bundle is not available.
+ return null;
+ }
+
+ /**
+ * Return `true` if content hashes for the [package] library files are the
+ * same the hashes in the unlinked [bundle].
+ */
+ bool _isConsistent(PubPackage package, PackageBundle bundle) {
+ List<String> actualHashes = <String>[];
+
+ /**
+ * If the given [file] is a Dart file, add its content hash.
+ */
+ void hashDartFile(File file) {
+ String path = file.path;
+ if (AnalysisEngine.isDartFileName(path)) {
+ List<int> fileBytes = file.readAsBytesSync();
+ List<int> hashBytes = md5.convert(fileBytes).bytes;
+ String hashHex = hex.encode(hashBytes);
+ actualHashes.add(hashHex);
+ }
+ }
+
+ /**
+ * Visit the [folder] recursively.
+ */
+ void hashDartFiles(Folder folder) {
+ List<Resource> children = folder.getChildren();
+ for (Resource child in children) {
+ if (child is File) {
+ hashDartFile(child);
+ } else if (child is Folder) {
+ hashDartFiles(child);
+ }
+ }
+ }
+
+ // Recursively compute hashes of the `lib` folder Dart files.
+ try {
+ hashDartFiles(package.libFolder);
+ } on FileSystemException {
+ return false;
+ }
+
+ // Compare sorted actual and bundle unit hashes.
+ List<String> bundleHashes = bundle.unlinkedUnitHashes.toList()..sort();
+ actualHashes.sort();
+ return listsEqual(actualHashes, bundleHashes);
+ }
+
+ /**
+ * Parse the given [source] into AST.
+ */
+ CompilationUnit _parse(Source source, bool strong) {
+ String code = source.contents.data;
+ AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER;
+ CharSequenceReader reader = new CharSequenceReader(code);
+ Scanner scanner = new Scanner(source, reader, errorListener);
+ scanner.scanGenericMethodComments = strong;
+ Token token = scanner.tokenize();
+ LineInfo lineInfo = new LineInfo(scanner.lineStarts);
+ Parser parser = new Parser(source, errorListener);
+ parser.parseGenericMethodComments = strong;
+ CompilationUnit unit = parser.parseCompilationUnit(token);
+ unit.lineInfo = lineInfo;
+ return unit;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Schedule computing unlinked bundles for the given [package].
+ */
+ void _scheduleUnlinked(PubPackage package) {
+ if (packagesToComputeUnlinked.isEmpty) {
+ _scheduleNextUnlinked();
+ }
+ packagesToComputeUnlinked.add(package);
+ }
+
+ /**
+ * 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 the given [uri] has the `package` scheme, return the name of the
+ * package that contains the referenced resource. Otherwise return `null`.
+ *
+ * For example `package:foo/bar.dart` => `foo`.
+ */
+ static String getPackageName(String uri) {
+ const String PACKAGE_SCHEME = 'package:';
+ if (uri.startsWith(PACKAGE_SCHEME)) {
+ int index = uri.indexOf('/');
+ if (index != -1) {
+ return uri.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;
+ }
+}
+
+class _ContextLinker {
+ final PubSummaryManager manager;
+ final AnalysisContext context;
+
+ final strong;
+ final _ListedPackages listedPackages;
+ final PackageBundle sdkBundle;
+
+ final List<_LinkNode> nodes = <_LinkNode>[];
+ final Map<String, _LinkNode> packageToNode = <String, _LinkNode>{};
+
+ _ContextLinker(this.manager, AnalysisContext context)
+ : context = context,
+ strong = context.analysisOptions.strongMode,
+ listedPackages = new _ListedPackages(context.sourceFactory),
+ sdkBundle = context.sourceFactory.dartSdk.getLinkedBundle();
+
+ /**
+ * Return the list of linked [LinkedPubPackage]s that can be provided at this
+ * time for a subset of the packages used by the [context].
+ */
+ List<LinkedPubPackage> getLinkedBundles() {
+// Stopwatch timer = new Stopwatch()..start();
+
+ if (sdkBundle == null) {
+ return const <LinkedPubPackage>[];
+ }
+
+ Map<PubPackage, PackageBundle> unlinkedBundles =
+ manager.getUnlinkedBundles(context);
+
+ // TODO(scheglov) remove debug output after optimizing
+// print('LOADED ${unlinkedBundles.length} unlinked bundles'
+// ' in ${timer.elapsedMilliseconds} ms');
+// timer..reset();
+
+ // If no unlinked bundles, there is nothing we can try to link.
+ if (unlinkedBundles.isEmpty) {
+ return const <LinkedPubPackage>[];
+ }
+
+ // Create nodes for packages.
+ unlinkedBundles.forEach((package, unlinked) {
+ _LinkNode node = new _LinkNode(this, package, unlinked);
+ nodes.add(node);
+ packageToNode[package.name] = node;
+ });
+
+ // Compute transitive dependencies, mark some nodes as failed.
+ for (_LinkNode node in nodes) {
+ node.computeTransitiveDependencies();
+ }
+
+ // Attempt to read existing linked bundles.
+ for (_LinkNode node in nodes) {
+ _readLinked(node);
+ }
+
+ // Link new packages, if allowed.
+ if (manager.allowLinking) {
+ _link();
+ }
+
+ // Create successfully linked packages.
+ List<LinkedPubPackage> linkedPackages = <LinkedPubPackage>[];
+ for (_LinkNode node in nodes) {
+ if (node.linked != null) {
+ linkedPackages.add(new LinkedPubPackage(
+ node.package, node.unlinked, node.linked, node.linkedHash));
+ }
+ }
+
+ // TODO(scheglov) remove debug output after optimizing
+// print('LINKED ${linkedPackages.length} bundles'
+// ' in ${timer.elapsedMilliseconds} ms');
+
+ // Done.
+ return linkedPackages;
+ }
+
+ String _getDeclaredVariable(String name) {
+ return context.declaredVariables.get(name);
+ }
+
+ /**
+ * Return the name of the file for a linked bundle, in strong or spec mode.
+ */
+ String _getLinkedName(String hash) {
+ if (strong) {
+ return 'linked_$hash.ds';
+ } else {
+ return 'linked_spec_$hash.ds';
+ }
+ }
+
+ void _link() {
+ // 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.unlinked);
+ if (node.linked != null) {
+ store.addBundle(null, node.linked);
+ }
+ }
+
+ // Prepare URIs to link.
+ Map<String, _LinkNode> uriToNode = <String, _LinkNode>{};
+ for (_LinkNode node in nodes) {
+ if (!node.isReady) {
+ for (String uri in node.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];
+ }, _getDeclaredVariable, strong);
+
+ // 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.linkedNewBytes = bytes;
+ node.linked = new PackageBundle.fromBuffer(bytes);
+ }
+ }
+
+ // Write newly linked bundles.
+ for (_LinkNode node in nodes) {
+ _writeLinked(node);
+ }
+ }
+
+ /**
+ * Attempt to find the linked bundle that corresponds to the given [node]
+ * with all its transitive dependencies and put it into [_LinkNode.linked].
+ */
+ void _readLinked(_LinkNode node) {
+ String hash = node.linkedHash;
+ if (hash != null) {
+ String fileName = _getLinkedName(hash);
+ File file = node.package.folder.getChildAssumingFile(fileName);
+ // Try to find in the cache.
+ PackageBundle linked = manager.linkedBundleMap[file.path];
+ if (linked != null) {
+ node.linked = linked;
+ return;
+ }
+ // Try to read from the file system.
+ if (file.exists) {
+ try {
+ List<int> bytes = file.readAsBytesSync();
+ linked = new PackageBundle.fromBuffer(bytes);
+ manager.linkedBundleMap[file.path] = linked;
+ node.linked = linked;
+ } on FileSystemException {
+ // Ignore file system exceptions.
+ }
+ }
+ }
+ }
+
+ /**
+ * 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) {
+ String hash = node.linkedHash;
+ if (hash != null && node.linkedNewBytes != null) {
+ String fileName = _getLinkedName(hash);
+ File file = node.package.folder.getChildAssumingFile(fileName);
+ manager.linkedBundleMap[file.path] = node.linked;
+ manager._writeAtomic(node.package.folder, fileName, node.linkedNewBytes);
+ }
+ }
+}
+
+/**
+ * Information about a package to link.
+ */
+class _LinkNode {
+ final _ContextLinker linker;
+ final PubPackage package;
+ final PackageBundle unlinked;
+
+ bool failed = false;
+ Set<_LinkNode> transitiveDependencies;
+
+ List<_LinkNode> _dependencies;
+ String _linkedHash;
+
+ List<int> linkedNewBytes;
+ PackageBundle linked;
+
+ _LinkNode(this.linker, this.package, this.unlinked);
+
+ /**
+ * 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 (uriStr.startsWith('package:')) {
+ String package = PubSummaryManager.getPackageName(uriStr);
+ _LinkNode packageNode = linker.packageToNode[package];
+ if (packageNode == null && linker.listedPackages.isListed(uriStr)) {
+ failed = true;
+ }
+ if (packageNode != null) {
+ dependencies.add(packageNode);
+ }
+ } else {
+ failed = true;
+ }
+ }
+
+ for (UnlinkedUnit unit in 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 => 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) {
+ ApiSignature signature = new ApiSignature();
+ // Add all unlinked API signatures.
+ List<String> signatures = <String>[];
+ signatures.add(linker.sdkBundle.apiSignature);
+ transitiveDependencies
+ .map((node) => node.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.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._getDeclaredVariable(name) ?? '');
+ }
+ }
+
+ /**
+ * Compute the set of existing transitive dependencies for this node.
+ * If any `package` dependency cannot be resolved, but it is one of the
+ * [listedPackages] then set [failed] to `true`.
+ * Only [unlinked] 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();
+}
+
+/**
+ * The set of package names that are listed in the `.packages` file of a
+ * context. These are the only packages, references to which can
+ * be possibly resolved in the context. Nodes that reference a `package:` URI
+ * without the unlinked bundle, so without the node, cannot be linked.
+ */
+class _ListedPackages {
+ final Set<String> names = new Set<String>();
+
+ _ListedPackages(SourceFactory sourceFactory) {
+ Map<String, List<Folder>> map = sourceFactory.packageMap;
+ if (map != null) {
+ names.addAll(map.keys);
+ }
+ }
+
+ /**
+ * Check whether the given `package:` [uri] is listed in the package map.
+ */
+ bool isListed(String uri) {
+ String package = PubSummaryManager.getPackageName(uri);
+ return names.contains(package);
+ }
+}
« no previous file with comments | « packages/analyzer/lib/src/summary/prelink.dart ('k') | packages/analyzer/lib/src/summary/public_namespace_computer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698