| OLD | NEW |
| (Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 import 'dart:convert' show UTF8; |
| 6 import 'dart:core' hide Resource; |
| 7 |
| 8 import 'package:analyzer/dart/element/element.dart'; |
| 9 import 'package:analyzer/file_system/file_system.dart'; |
| 10 import 'package:analyzer/src/generated/engine.dart'; |
| 11 import 'package:analyzer/src/generated/source.dart'; |
| 12 import 'package:analyzer/src/summary/format.dart'; |
| 13 import 'package:analyzer/src/summary/idl.dart'; |
| 14 import 'package:analyzer/src/summary/summarize_elements.dart'; |
| 15 import 'package:crypto/crypto.dart'; |
| 16 |
| 17 /** |
| 18 * The cache of per-library [PackageBundle]s. |
| 19 * |
| 20 * Note that currently this class is not intended for interactive use. |
| 21 */ |
| 22 class LibraryBundleCache { |
| 23 /** |
| 24 * To ensure that operations of writing files are atomic we create a temporary |
| 25 * file with this name in the [cacheFolder] and then rename it once we are |
| 26 * done writing. |
| 27 */ |
| 28 final String tempFileName; |
| 29 |
| 30 /** |
| 31 * The folder to read and write files. |
| 32 */ |
| 33 final Folder cacheFolder; |
| 34 |
| 35 /** |
| 36 * The context in which this cache is used. |
| 37 */ |
| 38 final AnalysisContext context; |
| 39 |
| 40 /** |
| 41 * Opaque data that reflects the current configuration, such as the [context] |
| 42 * options, and is mixed into the hashes. |
| 43 */ |
| 44 final List<int> configSalt; |
| 45 |
| 46 final Map<Source, CacheLibraryUris> _libraryUrisMap = |
| 47 <Source, CacheLibraryUris>{}; |
| 48 final Map<Source, List<Source>> _libraryClosureMap = <Source, List<Source>>{}; |
| 49 final Map<Source, List<int>> _sourceContentHashMap = <Source, List<int>>{}; |
| 50 |
| 51 LibraryBundleCache( |
| 52 this.tempFileName, this.cacheFolder, this.context, this.configSalt); |
| 53 |
| 54 /** |
| 55 * Clear internal caches so that we read from file system again. |
| 56 */ |
| 57 void clearInternalCaches() { |
| 58 _libraryUrisMap.clear(); |
| 59 _libraryClosureMap.clear(); |
| 60 _sourceContentHashMap.clear(); |
| 61 } |
| 62 |
| 63 /** |
| 64 * Write information about the [library] into the cache. |
| 65 */ |
| 66 void putLibrary(LibraryElement library) { |
| 67 try { |
| 68 _writeUris(library); |
| 69 List<int> hash = _getLibraryClosureHash(library.source); |
| 70 String hashStr = CryptoUtils.bytesToHex(hash); |
| 71 PackageBundleAssembler assembler = new PackageBundleAssembler(); |
| 72 assembler.serializeLibraryElement(library); |
| 73 List<int> bytes = assembler.assemble().toBuffer(); |
| 74 _safeWriteBytes('$hashStr.sum', bytes); |
| 75 } catch (e) {} |
| 76 } |
| 77 |
| 78 /** |
| 79 * Read the [PackageBundle] for the library with the given [source] from |
| 80 * the cache. The returned bundle will correspond to the state when the set |
| 81 * of direct and indirect dependencies is resolved in the [context]. Return |
| 82 * `null` if such bundle does not exist. |
| 83 */ |
| 84 PackageBundle readBundle(Source source) { |
| 85 try { |
| 86 List<int> hash = _getLibraryClosureHash(source); |
| 87 String hashStr = CryptoUtils.bytesToHex(hash); |
| 88 List<int> bytes = _safeReadBytes('$hashStr.sum'); |
| 89 if (bytes == null) { |
| 90 return null; |
| 91 } |
| 92 return new PackageBundle.fromBuffer(bytes); |
| 93 } catch (e) { |
| 94 return null; |
| 95 } |
| 96 } |
| 97 |
| 98 /** |
| 99 * Fill the whole source closure of the library with the given |
| 100 * [librarySource]. It includes defining units and parts of the library and |
| 101 * all its directly or indirectly imported or exported libraries. |
| 102 */ |
| 103 void _appendLibraryClosure(Set<Source> closure, Source librarySource) { |
| 104 if (closure.add(librarySource)) { |
| 105 CacheLibraryUris libraryUris = _getUris(librarySource); |
| 106 if (libraryUris == null) { |
| 107 throw new StateError('No URIs for $librarySource'); |
| 108 } |
| 109 // Append parts. |
| 110 for (String partUri in libraryUris.partUris) { |
| 111 Source partSource = |
| 112 context.sourceFactory.resolveUri(librarySource, partUri); |
| 113 if (partSource == null) { |
| 114 throw new StateError('Unable to resolve $partUri in $librarySource'); |
| 115 } |
| 116 closure.add(partSource); |
| 117 } |
| 118 // Append imports and exports. |
| 119 void appendLibrarySources(String refUri) { |
| 120 Source refSource = |
| 121 context.sourceFactory.resolveUri(librarySource, refUri); |
| 122 if (refSource == null) { |
| 123 throw new StateError('Unable to resolve $refUri in $librarySource'); |
| 124 } |
| 125 _appendLibraryClosure(closure, refSource); |
| 126 } |
| 127 libraryUris.importedUris.forEach(appendLibrarySources); |
| 128 libraryUris.exportedUris.forEach(appendLibrarySources); |
| 129 } |
| 130 } |
| 131 |
| 132 /** |
| 133 * Return the whole source closure of the library with the given |
| 134 * [librarySource]. It includes defining units and parts of the library and |
| 135 * of all its directly or indirectly imported or exported libraries. |
| 136 */ |
| 137 List<Source> _getLibraryClosure(Source librarySource) { |
| 138 return _libraryClosureMap.putIfAbsent(librarySource, () { |
| 139 Set<Source> closure = new Set<Source>(); |
| 140 _appendLibraryClosure(closure, librarySource); |
| 141 return closure.toList(); |
| 142 }); |
| 143 } |
| 144 |
| 145 /** |
| 146 * Return the [context]-specific hash of the closure of the library with |
| 147 * the given [librarySource]. |
| 148 */ |
| 149 List<int> _getLibraryClosureHash(Source librarySource) { |
| 150 List<Source> closure = _getLibraryClosure(librarySource); |
| 151 MD5 md5 = new MD5(); |
| 152 for (Source source in closure) { |
| 153 List<int> sourceHash = _getSourceContentHash(source); |
| 154 md5.add(sourceHash); |
| 155 } |
| 156 md5.add(configSalt); |
| 157 return md5.close(); |
| 158 } |
| 159 |
| 160 /** |
| 161 * Compute a hash of the given [source] contents. |
| 162 */ |
| 163 List<int> _getSourceContentHash(Source source) { |
| 164 return _sourceContentHashMap.putIfAbsent(source, () { |
| 165 String sourceText = source.contents.data; |
| 166 List<int> sourceBytes = UTF8.encode(sourceText); |
| 167 return (new MD5()..add(sourceBytes)).close(); |
| 168 }); |
| 169 } |
| 170 |
| 171 /** |
| 172 * Get the URIs information of the library with the given [librarySource], |
| 173 * maybe `null` if the information is not in the cache. |
| 174 */ |
| 175 CacheLibraryUris _getUris(Source librarySource) { |
| 176 CacheLibraryUris uris = _libraryUrisMap[librarySource]; |
| 177 if (uris == null) { |
| 178 String fileName = _getUrisFileName(librarySource); |
| 179 List<int> bytes = _safeReadBytes(fileName); |
| 180 if (bytes == null) { |
| 181 return null; |
| 182 } |
| 183 uris = new CacheLibraryUris.fromBuffer(bytes); |
| 184 _libraryUrisMap[librarySource] = uris; |
| 185 } |
| 186 return uris; |
| 187 } |
| 188 |
| 189 /** |
| 190 * Return the name of the file with the [librarySource] URIs information. |
| 191 */ |
| 192 String _getUrisFileName(Source librarySource) { |
| 193 List<int> hash = _getSourceContentHash(librarySource); |
| 194 String hashStr = CryptoUtils.bytesToHex(hash); |
| 195 return '$hashStr.uris'; |
| 196 } |
| 197 |
| 198 /** |
| 199 * Return bytes of the file with the given [relPath] in the cache, or `null` |
| 200 * if the file does not exist. |
| 201 */ |
| 202 List<int> _safeReadBytes(String relPath) { |
| 203 Resource urisFile = cacheFolder.getChild(relPath); |
| 204 if (urisFile is File) { |
| 205 try { |
| 206 return urisFile.readAsBytesSync(); |
| 207 } on FileSystemException {} |
| 208 } |
| 209 return null; |
| 210 } |
| 211 |
| 212 /** |
| 213 * Atomically write the given [bytes] into the file with the given [relPath]. |
| 214 * Silently ignores any errors. |
| 215 */ |
| 216 void _safeWriteBytes(String relPath, List<int> bytes) { |
| 217 try { |
| 218 String absPath = cacheFolder.getChild(relPath).path; |
| 219 File tempFile = cacheFolder.getChild(tempFileName); |
| 220 tempFile.writeAsBytesSync(bytes); |
| 221 tempFile.renameSync(absPath); |
| 222 } catch (e) {} |
| 223 } |
| 224 |
| 225 /** |
| 226 * Write URIs information for the given [library] and its direct and |
| 227 * indirect imports/exports. |
| 228 */ |
| 229 void _writeUris(LibraryElement library, |
| 230 [Set<LibraryElement> writtenLibraries]) { |
| 231 Source librarySource = library.source; |
| 232 // Do nothing if already cached. |
| 233 if (_libraryUrisMap.containsKey(librarySource)) { |
| 234 return; |
| 235 } |
| 236 // Stop recursion cycle. |
| 237 writtenLibraries ??= new Set<LibraryElement>(); |
| 238 if (!writtenLibraries.add(library)) { |
| 239 return; |
| 240 } |
| 241 // Prepare import/export URIs. |
| 242 List<String> importUris = <String>[]; |
| 243 List<String> exportUris = <String>[]; |
| 244 for (ImportElement element in library.imports) { |
| 245 String uri = element.uri; |
| 246 if (uri != null) { |
| 247 importUris.add(uri); |
| 248 _writeUris(element.importedLibrary, writtenLibraries); |
| 249 } |
| 250 } |
| 251 for (ExportElement element in library.exports) { |
| 252 String uri = element.uri; |
| 253 if (uri != null) { |
| 254 exportUris.add(uri); |
| 255 _writeUris(element.exportedLibrary, writtenLibraries); |
| 256 } |
| 257 } |
| 258 // Write the URIs. |
| 259 CacheLibraryUrisBuilder b = new CacheLibraryUrisBuilder( |
| 260 importedUris: importUris, |
| 261 exportedUris: exportUris, |
| 262 partUris: library.parts.map((e) => e.uri).toList()); |
| 263 List<int> bytes = b.toBuffer(); |
| 264 String fileName = _getUrisFileName(librarySource); |
| 265 _safeWriteBytes(fileName, bytes); |
| 266 // Put into the cache to avoid reading it later. |
| 267 _libraryUrisMap[librarySource] = new CacheLibraryUris.fromBuffer(bytes); |
| 268 } |
| 269 } |
| OLD | NEW |