Serialize and restore file dependencies too

This commit is contained in:
Simon Binder 2023-03-25 16:29:07 +01:00
parent e51bb0976e
commit a6549425ef
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
8 changed files with 103 additions and 15 deletions

View File

@ -1,6 +1,7 @@
## 2.7.0-dev
- Make `validateDatabaseSchema()` work in migration tests.
- Fix elements from transitive imports in drift files not being added reliably.
## 2.6.0

View File

@ -5,7 +5,7 @@ import 'state.dart';
///
/// At the moment, the cache is not set up to handle changing files.
class DriftAnalysisCache {
final Map<Uri, Map<String, Object?>> serializedElements = {};
final Map<Uri, CachedSerializationResult> serializationCache = {};
final Map<Uri, FileState> knownFiles = {};
final Map<DriftElementId, DiscoveredElement> discoveredElements = {};
@ -16,7 +16,7 @@ class DriftAnalysisCache {
FileState notifyFileChanged(Uri uri) {
// todo: Mark references for files that import this one as stale.
// todo: Mark elements that reference an element in this file as stale.
serializedElements.remove(uri);
serializationCache.remove(uri);
return knownFiles.putIfAbsent(uri, () => FileState(uri))
..errorsDuringDiscovery.clear()
@ -62,8 +62,7 @@ class DriftAnalysisCache {
final found = pending.removeLast();
yield found;
for (final imported
in found.discovery?.importDependencies ?? const <Uri>[]) {
for (final imported in found.imports ?? const <Uri>[]) {
if (seenUris.add(imported)) {
pending.add(knownFiles[imported]!);
}
@ -71,3 +70,10 @@ class DriftAnalysisCache {
}
}
}
class CachedSerializationResult {
final List<Uri> cachedImports;
final Map<String, Map<String, Object?>> cachedElements;
CachedSerializationResult(this.cachedImports, this.cachedElements);
}

View File

@ -98,8 +98,8 @@ class DriftAnalysisDriver {
///
/// Returns non-null if analysis results were found and successfully restored.
Future<Map<String, Object?>?> readStoredAnalysisResult(Uri uri) async {
final cached = cache.serializedElements[uri];
if (cached != null) return cached;
final cached = cache.serializationCache[uri];
if (cached != null) return cached.cachedElements;
// Not available in in-memory cache, so let's read it from the file system.
final reader = cacheReader;
@ -109,7 +109,15 @@ class DriftAnalysisDriver {
if (found == null) return null;
final parsed = json.decode(found) as Map<String, Object?>;
return cache.serializedElements[uri] = parsed;
final data = CachedSerializationResult(
[
for (final entry in parsed['imports'] as List)
Uri.parse(entry as String)
],
(parsed['elements'] as Map<String, Object?>).cast(),
);
cache.serializationCache[uri] = data;
return data.cachedElements;
}
Future<bool> _recoverFromCache(FileState state) async {
@ -128,6 +136,21 @@ class DriftAnalysisDriver {
}
}
final cachedImports = cache.serializationCache[state.ownUri]?.cachedImports;
if (cachedImports != null && state.discovery == null) {
state.cachedImports = cachedImports;
for (final import in cachedImports) {
final found = cache.stateForUri(import);
if (found.imports == null) {
// Attempt to recover this file as well to make sure we know the
// imports for every file transitively reachable from the sources
// analyzed.
await _recoverFromCache(found);
}
}
}
return allRecovered;
}
@ -231,6 +254,24 @@ class DriftAnalysisDriver {
return state;
}
/// Serializes imports and locally-defined elements of the file.
///
/// Serialized data can later be recovered if a [cacheReader] is set on this
/// driver, which avoids running duplicate analysis runs across build steps.
SerializedElements serializeState(FileState state) {
final data = ElementSerializer.serialize(
state.analysis.values.map((e) => e.result).whereType());
final imports = state.discovery?.importDependencies;
if (imports != null) {
data.serializedData['imports'] = [
for (final import in imports) import.toString()
];
}
return data;
}
}
/// Reads serialized data and a generated Dart helper file used to serialize

View File

@ -15,6 +15,8 @@ class FileState {
final Uri ownUri;
DiscoveredFileState? discovery;
List<Uri>? cachedImports;
final List<DriftAnalysisError> errorsDuringDiscovery = [];
final Map<DriftElementId, ElementAnalysisState> analysis = {};
@ -24,6 +26,8 @@ class FileState {
FileState(this.ownUri);
Iterable<Uri>? get imports => discovery?.importDependencies ?? cachedImports;
String get extension => url.extension(ownUri.path);
/// Whether this file contains a drift database or a drift accessor / DAO.

View File

@ -10,9 +10,13 @@ import 'results/results.dart';
class SerializedElements {
final List<AnnotatedDartCode> dartTypes;
final Map<String, Object?> serializedElements;
final Map<String, Object?> serializedData;
final Map<String, Object?> _serializedElements;
SerializedElements(this.dartTypes, this.serializedElements);
SerializedElements(
this.dartTypes, this.serializedData, this._serializedElements) {
serializedData['elements'] = _serializedElements;
}
}
/// Serializes [DriftElement]s to JSON.
@ -22,13 +26,13 @@ class SerializedElements {
/// a single file changes). However, it means that we have to serialize analysis
/// results to read them back in in a later build step.
class ElementSerializer {
final SerializedElements _result = SerializedElements([], {});
final SerializedElements _result = SerializedElements([], {}, {});
ElementSerializer._();
void _serializeElements(Iterable<DriftElement> elements) {
for (final element in elements) {
_result.serializedElements[element.id.name] = _serialize(element);
_result._serializedElements[element.id.name] = _serialize(element);
}
}

View File

@ -3,7 +3,6 @@ import 'dart:convert';
import 'package:build/build.dart';
import '../../analysis/driver/driver.dart';
import '../../analysis/serializer.dart';
import '../../analysis/options.dart';
import '../../writer/import_manager.dart';
import '../../writer/writer.dart';
@ -45,10 +44,9 @@ class DriftAnalyzer extends Builder {
}
}
final serialized = ElementSerializer.serialize(
results.analysis.values.map((e) => e.result).whereType());
final serialized = driver.serializeState(results);
final asJson =
JsonUtf8Encoder(' ' * 2).convert(serialized.serializedElements);
JsonUtf8Encoder(' ' * 2).convert(serialized.serializedData);
final jsonOutput = buildStep.inputId.addExtension('.drift_module.json');
final typesOutput = buildStep.inputId.addExtension('.types.temp.dart');

View File

@ -347,4 +347,37 @@ class Database extends $Database {}
'a|lib/db.drift.dart': decodedMatches(contains(r'.$drift0];'))
}, result.dartOutputs, result);
});
test('writes query from transitive import', () async {
final result = await emulateDriftBuild(
inputs: {
'a|lib/main.dart': '''
import 'package:drift/drift.dart';
@DriftDatabase(include: {'a.drift'})
class MyDatabase {}
''',
'a|lib/a.drift': '''
import 'b.drift';
CREATE TABLE foo (bar INTEGER);
''',
'a|lib/b.drift': '''
import 'c.drift';
CREATE TABLE foo2 (bar INTEGER);
''',
'a|lib/c.drift': '''
q: SELECT 1;
''',
},
logger: loggerThat(neverEmits(anything)),
);
checkOutputs({
'a|lib/main.drift.dart': decodedMatches(
contains(r'Selectable<int> q()'),
)
}, result.dartOutputs, result);
});
}

View File

@ -35,6 +35,7 @@ abstract class $Database extends i0.GeneratedDatabase {
likes,
follows,
popularUsers,
i1.usersName,
i4.$drift0
];
@override