| OLD | NEW |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import 'dart:convert'; | 5 import 'dart:convert'; |
| 6 | 6 |
| 7 import 'package:analyzer/file_system/file_system.dart'; | 7 import 'package:analyzer/file_system/file_system.dart'; |
| 8 import 'package:analyzer/src/generated/engine.dart'; | 8 import 'package:analyzer/src/generated/engine.dart'; |
| 9 import 'package:analyzer/src/generated/source.dart'; | 9 import 'package:analyzer/src/generated/source.dart'; |
| 10 import 'package:analyzer/src/generated/utilities_collection.dart'; | 10 import 'package:analyzer/src/generated/utilities_collection.dart'; |
| 11 import 'package:analyzer/src/summary/idl.dart'; | 11 import 'package:analyzer/src/summary/idl.dart'; |
| 12 import 'package:convert/convert.dart'; | 12 import 'package:convert/convert.dart'; |
| 13 import 'package:crypto/crypto.dart'; | 13 import 'package:crypto/crypto.dart'; |
| 14 import 'package:meta/meta.dart'; | 14 import 'package:meta/meta.dart'; |
| 15 | 15 |
| 16 /** | 16 /** |
| 17 * Return the path of the directory where bundles for the given [uri] should be | 17 * Return the [Folder] where bundles for the given [absoluteUri] should be |
| 18 * looked for. This directory should contain corresponding pairs of `*.api.ds` | 18 * looked for. This folder should contain corresponding `*.full.ds` files, |
| 19 * and `*.full.ds` files, possibly more than one pair. Return `null` if the | 19 * possibly more than one one. Return `null` if the given [absoluteUri] |
| 20 * given [uri] does not have the expected structure, so the output path cannot | 20 * does not have the expected structure, so the output path cannot be computed. |
| 21 * be computed. | |
| 22 */ | 21 */ |
| 23 typedef String GetOutputPath(ResourceProvider provider, Uri uri); | 22 typedef Folder GetOutputFolder(Uri absoluteUri); |
| 24 | 23 |
| 25 /** | 24 /** |
| 26 * Information about a Dart package in Bazel. | 25 * Information about a Dart package in Bazel. |
| 27 */ | 26 */ |
| 28 class Package { | 27 class Package { |
| 29 final String bundlePath; | 28 final File unlinkedFile; |
| 30 final PackageBundle bundle; | 29 final PackageBundle unlinked; |
| 31 final Set<String> _unitUris = new Set<String>(); | 30 final Set<String> _unitUris = new Set<String>(); |
| 32 | 31 |
| 33 Package(this.bundlePath, this.bundle) { | 32 Package(this.unlinkedFile, this.unlinked) { |
| 34 _unitUris.addAll(bundle.unlinkedUnitUris); | 33 _unitUris.addAll(unlinked.unlinkedUnitUris); |
| 35 } | 34 } |
| 36 } | 35 } |
| 37 | 36 |
| 38 /** | 37 /** |
| 39 * Class that reads summaries of Bazel packages. | 38 * Class that reads summaries of Bazel packages. |
| 40 * | 39 * |
| 41 * When the client needs to produce a resolution result for a new [Source], it | 40 * When the client needs to produce a resolution result for a new [Source], it |
| 42 * should call [getPackages] to checked whether there is the set of packages | 41 * should call [getLinkedPackages] to check whether there is the set of |
| 43 * to resynthesize resolution results. | 42 * packages to resynthesize resolution results. |
| 44 */ | 43 */ |
| 45 class SummaryProvider { | 44 class SummaryProvider { |
| 46 final ResourceProvider provider; | 45 final ResourceProvider provider; |
| 47 final GetOutputPath getOutputPath; | 46 final GetOutputFolder getOutputFolder; |
| 48 final AnalysisContext context; | 47 final AnalysisContext context; |
| 49 | 48 |
| 50 /** | 49 /** |
| 51 * Mapping from bundle paths to corresponding [Package]s. The packages in | 50 * Mapping from bundle paths to corresponding [Package]s. The packages in |
| 52 * the map were consistent with their constituent sources at the moment when | 51 * the map were consistent with their constituent sources at the moment when |
| 53 * they were put into the map. | 52 * they were put into the map. |
| 54 */ | 53 */ |
| 55 final Map<String, Package> bundlePathToPackageMap = <String, Package>{}; | 54 final Map<Folder, List<Package>> folderToPackagesMap = {}; |
| 56 | 55 |
| 57 /** | 56 SummaryProvider(this.provider, this.getOutputFolder, this.context); |
| 58 * When we detected than some bundle is not consistent with its constituent | |
| 59 * sources (i.e. even its unlinked state is not consistent), we remember | |
| 60 * this fact to avoid loading and checking consistency next time. | |
| 61 */ | |
| 62 final Set<String> knownInconsistentBundlePaths = new Set<String>(); | |
| 63 | |
| 64 SummaryProvider(this.provider, this.getOutputPath, this.context); | |
| 65 | |
| 66 /** | |
| 67 * Return the [Package] that contains information about the source with | |
| 68 * the given [uri], or `null` if such package does not exist. | |
| 69 */ | |
| 70 @visibleForTesting | |
| 71 Package getPackageForUri(Uri uri) { | |
| 72 String outputPath = getOutputPath(provider, uri); | |
| 73 if (outputPath != null) { | |
| 74 List<Package> packages = _getPackages(outputPath); | |
| 75 for (Package package in packages) { | |
| 76 String uriStr = uri.toString(); | |
| 77 if (package._unitUris.contains(uriStr)) { | |
| 78 return package; | |
| 79 } | |
| 80 } | |
| 81 } | |
| 82 return null; | |
| 83 } | |
| 84 | 57 |
| 85 /** | 58 /** |
| 86 * Return the complete list of [Package]s that are required to provide all | 59 * Return the complete list of [Package]s that are required to provide all |
| 87 * resolution results for the given [source]. | 60 * resolution results for the given [source]. |
| 88 * | 61 * |
| 89 * The same list of packages is returned for the same [Source], i.e. always | 62 * The same list of packages is returned for the same [Source], i.e. always |
| 90 * the full list, not a difference with a previous request. It is up to the | 63 * the full list, not a difference with a previous request. It is up to the |
| 91 * client to decide whether some of the returned packages should be excluded | 64 * client to decide whether some of the returned packages should be excluded |
| 92 * as already mixed into a resynthesizer. | 65 * as already mixed into a resynthesizer. |
| 93 * | 66 * |
| 94 * If the full set of packages cannot be produced, for example because some | 67 * If the full set of packages cannot be produced, for example because some |
| 95 * bundles are not built, or out of date, etc, then `null` is returned. | 68 * bundles are not built, or out of date, etc, then `null` is returned. |
| 96 */ | 69 */ |
| 97 List<PackageBundle> getPackages(Source source) { | 70 List<Package> getLinkedPackages(Source source) { |
| 98 // TODO(scheglov) implement | 71 // TODO(scheglov) implement |
| 99 return null; | 72 return null; |
| 100 } | 73 } |
| 101 | 74 |
| 102 /** | 75 /** |
| 103 * Return the hexadecimal string for the given [source] contents. | 76 * Return the [Package] that contains information about the source with |
| 77 * the given [uri], or `null` if such package does not exist. |
| 78 */ |
| 79 @visibleForTesting |
| 80 Package getUnlinkedForUri(Uri uri) { |
| 81 Folder outputFolder = getOutputFolder(uri); |
| 82 if (outputFolder != null) { |
| 83 String uriStr = uri.toString(); |
| 84 List<Package> packages = _getUnlinkedPackages(outputFolder); |
| 85 for (Package package in packages) { |
| 86 if (package._unitUris.contains(uriStr)) { |
| 87 return package; |
| 88 } |
| 89 } |
| 90 } |
| 91 return null; |
| 92 } |
| 93 |
| 94 /** |
| 95 * Return the hexadecimal string of the MD5 hash of the contents of the |
| 96 * given [source] in [context]. |
| 104 */ | 97 */ |
| 105 String _computeSourceHashHex(Source source) { | 98 String _computeSourceHashHex(Source source) { |
| 106 String text = context.getContents(source).data; | 99 String text = context.getContents(source).data; |
| 107 List<int> bytes = UTF8.encode(text); | 100 List<int> bytes = UTF8.encode(text); |
| 108 List<int> hashBytes = md5.convert(bytes).bytes; | 101 List<int> hashBytes = md5.convert(bytes).bytes; |
| 109 return hex.encode(hashBytes); | 102 return hex.encode(hashBytes); |
| 110 } | 103 } |
| 111 | 104 |
| 112 /** | 105 /** |
| 113 * Return the [Package] from the file with the given [path], or `null` if the | 106 * Return all consistent unlinked [Package]s in the given [folder]. Some of |
| 114 * file does not exist, or it cannot be read, or is not consistent with the | 107 * the returned packages might be already linked. |
| 115 * sources it contains, etc. | |
| 116 */ | 108 */ |
| 117 Package _getPackage(String path) { | 109 List<Package> _getUnlinkedPackages(Folder folder) { |
| 118 // Check if the bundle know to be inconsistent, missing, etc. | 110 List<Package> packages = folderToPackagesMap[folder]; |
| 119 if (knownInconsistentBundlePaths.contains(path)) { | 111 if (packages == null) { |
| 120 return null; | 112 packages = <Package>[]; |
| 121 } | 113 try { |
| 122 // Attempt to get from the cache or read from the file system. | 114 List<Resource> children = folder.getChildren(); |
| 123 try { | 115 for (Resource child in children) { |
| 124 Package package = bundlePathToPackageMap[path]; | 116 if (child is File) { |
| 125 if (package == null) { | 117 String packagePath = child.path; |
| 126 File file = provider.getFile(path); | 118 if (packagePath.toLowerCase().endsWith('.full.ds')) { |
| 127 List<int> bytes = file.readAsBytesSync(); | 119 Package package = _readUnlinkedPackage(child); |
| 128 PackageBundle bundle = new PackageBundle.fromBuffer(bytes); | 120 if (package != null) { |
| 129 // Check for consistency, and fail if it's not. | 121 packages.add(package); |
| 130 if (!_isUnlinkedBundleConsistent(bundle)) { | 122 } |
| 131 knownInconsistentBundlePaths.add(path); | |
| 132 return null; | |
| 133 } | |
| 134 // OK, put the package into the cache. | |
| 135 package = new Package(path, bundle); | |
| 136 bundlePathToPackageMap[path] = package; | |
| 137 } | |
| 138 return package; | |
| 139 } catch (_) { | |
| 140 return null; | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 /** | |
| 145 * Return all consistent [Package]s in the given [folderPath]. | |
| 146 */ | |
| 147 List<Package> _getPackages(String folderPath) { | |
| 148 List<Package> packages = <Package>[]; | |
| 149 try { | |
| 150 Folder folder = provider.getFolder(folderPath); | |
| 151 List<Resource> children = folder.getChildren(); | |
| 152 for (Resource child in children) { | |
| 153 if (child is File) { | |
| 154 String packagePath = child.path; | |
| 155 if (packagePath.toLowerCase().endsWith('.full.ds')) { | |
| 156 Package package = _getPackage(packagePath); | |
| 157 if (package != null) { | |
| 158 packages.add(package); | |
| 159 } | 123 } |
| 160 } | 124 } |
| 161 } | 125 } |
| 162 } | 126 } on FileSystemException {} |
| 163 } on FileSystemException {} | 127 folderToPackagesMap[folder] = packages; |
| 128 } |
| 164 return packages; | 129 return packages; |
| 165 } | 130 } |
| 166 | 131 |
| 167 /** | 132 /** |
| 168 * Return `true` if the unlinked information of the [bundle] is consistent | 133 * Return `true` if the unlinked information of the [bundle] is consistent |
| 169 * with its constituent sources. | 134 * with its constituent sources in [context]. |
| 170 */ | 135 */ |
| 171 bool _isUnlinkedBundleConsistent(PackageBundle bundle) { | 136 bool _isUnlinkedBundleConsistent(PackageBundle bundle) { |
| 172 try { | 137 try { |
| 173 // Compute hashes of the constituent sources. | 138 // Compute hashes of the constituent sources. |
| 174 List<String> actualHashes = <String>[]; | 139 List<String> actualHashes = <String>[]; |
| 175 for (String uri in bundle.unlinkedUnitUris) { | 140 for (String uri in bundle.unlinkedUnitUris) { |
| 176 Source source = context.sourceFactory.resolveUri(null, uri); | 141 Source source = context.sourceFactory.resolveUri(null, uri); |
| 177 if (source == null) { | 142 if (source == null) { |
| 178 return false; | 143 return false; |
| 179 } | 144 } |
| 180 String hash = _computeSourceHashHex(source); | 145 String hash = _computeSourceHashHex(source); |
| 181 actualHashes.add(hash); | 146 actualHashes.add(hash); |
| 182 } | 147 } |
| 183 // Compare sorted actual and bundle unit hashes. | 148 // Compare sorted actual and bundle unit hashes. |
| 184 List<String> bundleHashes = bundle.unlinkedUnitHashes.toList()..sort(); | 149 List<String> bundleHashes = bundle.unlinkedUnitHashes.toList()..sort(); |
| 185 actualHashes.sort(); | 150 actualHashes.sort(); |
| 186 return listsEqual(actualHashes, bundleHashes); | 151 return listsEqual(actualHashes, bundleHashes); |
| 187 } catch (_) { | 152 } on FileSystemException {} |
| 188 return false; | 153 return false; |
| 189 } | 154 } |
| 155 |
| 156 /** |
| 157 * Read the unlinked [Package] from the given [file], or return `null` if the |
| 158 * file does not exist, or it cannot be read, or is not consistent with the |
| 159 * constituent sources on the file system. |
| 160 */ |
| 161 Package _readUnlinkedPackage(File file) { |
| 162 try { |
| 163 List<int> bytes = file.readAsBytesSync(); |
| 164 PackageBundle bundle = new PackageBundle.fromBuffer(bytes); |
| 165 // Check for consistency, and fail if it's not. |
| 166 if (!_isUnlinkedBundleConsistent(bundle)) { |
| 167 return null; |
| 168 } |
| 169 // OK, use the bundle. |
| 170 return new Package(file, bundle); |
| 171 } on FileSystemException {} |
| 172 return null; |
| 190 } | 173 } |
| 191 } | 174 } |
| OLD | NEW |