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

Side by Side Diff: pkg/analyzer/lib/src/summary/incremental_cache.dart

Issue 1807673006: Initial LibraryBundleCache implementation. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: tweaks Created 4 years, 9 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 unified diff | Download patch
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « pkg/analyzer/lib/src/summary/idl.dart ('k') | pkg/analyzer/test/src/summary/incremental_cache_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698