Start generating code from new analysis results

This commit is contained in:
Simon Binder 2022-09-17 23:14:52 +02:00
parent a6caae9d8f
commit 5947956109
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
20 changed files with 322 additions and 353 deletions

View File

@ -1,4 +1,5 @@
// This field is analyzed by drift_dev to easily obtain common types. // This field is analyzed by drift_dev to easily obtain common types.
export 'runtime/types/converters.dart' show TypeConverter, JsonTypeConverter; export 'runtime/types/converters.dart' show TypeConverter, JsonTypeConverter;
export 'runtime/query_builder/query_builder.dart' show TableInfo;
export 'dsl/dsl.dart' show Table, DriftDatabase, DriftAccessor; export 'dsl/dsl.dart' show Table, DriftDatabase, DriftAccessor;

View File

@ -7,10 +7,11 @@ Builder preparingBuilder(BuilderOptions options) => PreprocessBuilder();
Builder analyzer(BuilderOptions options) => DriftAnalyzer(options); Builder analyzer(BuilderOptions options) => DriftAnalyzer(options);
Builder driftBuilder(BuilderOptions options) => DriftSharedPartBuilder(options); Builder driftBuilder(BuilderOptions options) =>
DriftBuilder(DriftGenerationMode.monolithicSharedPart, options);
Builder driftBuilderNotShared(BuilderOptions options) => Builder driftBuilderNotShared(BuilderOptions options) =>
DriftPartBuilder(options); DriftBuilder(DriftGenerationMode.monolithicPart, options);
PostProcessBuilder driftCleanup(BuilderOptions options) { PostProcessBuilder driftCleanup(BuilderOptions options) {
return const FileDeletingBuilder(['.temp.dart']); return const FileDeletingBuilder(['.temp.dart']);

View File

@ -2,12 +2,18 @@ import '../results/element.dart';
import 'state.dart'; import 'state.dart';
class DriftAnalysisCache { class DriftAnalysisCache {
final Map<Uri, Map<String, Object?>> serializedElements = {};
final Map<Uri, FileState> knownFiles = {}; final Map<Uri, FileState> knownFiles = {};
final Map<DriftElementId, DiscoveredElement> discoveredElements = {}; final Map<DriftElementId, DiscoveredElement> discoveredElements = {};
FileState stateForUri(Uri uri) {
return knownFiles[uri] ?? notifyFileChanged(uri);
}
FileState notifyFileChanged(Uri uri) { FileState notifyFileChanged(Uri uri) {
// todo: Mark references for files that import this one as stale. // todo: Mark references for files that import this one as stale.
// todo: Mark elements that reference an element in this file as stale. // todo: Mark elements that reference an element in this file as stale.
serializedElements.remove(uri);
return knownFiles.putIfAbsent(uri, () => FileState(uri)) return knownFiles.putIfAbsent(uri, () => FileState(uri))
..errorsDuringDiscovery.clear() ..errorsDuringDiscovery.clear()

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:sqlparser/sqlparser.dart'; import 'package:sqlparser/sqlparser.dart';
import '../../analyzer/options.dart'; import '../../analyzer/options.dart';
@ -7,6 +9,8 @@ import '../resolver/dart/helper.dart';
import '../resolver/discover.dart'; import '../resolver/discover.dart';
import '../resolver/drift/sqlparser/mapping.dart'; import '../resolver/drift/sqlparser/mapping.dart';
import '../resolver/resolver.dart'; import '../resolver/resolver.dart';
import '../results/results.dart';
import '../serializer.dart';
import 'cache.dart'; import 'cache.dart';
import 'error.dart'; import 'error.dart';
import 'state.dart'; import 'state.dart';
@ -17,6 +21,9 @@ class DriftAnalysisDriver {
final DriftOptions options; final DriftOptions options;
late final TypeMapping typeMapping = TypeMapping(options); late final TypeMapping typeMapping = TypeMapping(options);
late final ElementDeserializer deserializer = ElementDeserializer(this);
AnalysisResultCacheReader? cacheReader;
KnownDriftTypes? _knownTypes; KnownDriftTypes? _knownTypes;
@ -43,10 +50,45 @@ class DriftAnalysisDriver {
return _knownTypes ??= await KnownDriftTypes.resolve(this); return _knownTypes ??= await KnownDriftTypes.resolve(this);
} }
Future<FileState> prepareFileForAnalysis(Uri uri) async { Future<Map<String, Object?>?> readStoredAnalysisResult(Uri uri) async {
final cached = cache.serializedElements[uri];
if (cached != null) return cached;
// Not available in in-memory cache, so let's read it from the file system.
final reader = cacheReader;
if (reader == null) return null;
final found = await reader.readCacheFor(uri);
if (found == null) return null;
final parsed = json.decode(found) as Map<String, Object?>;
return cache.serializedElements[uri] = parsed;
}
Future<bool> _recoverFromCache(FileState state) async {
final stored = await readStoredAnalysisResult(state.ownUri);
if (stored == null) return false;
var allRecovered = true;
for (final local in stored.keys) {
final id = DriftElementId(state.ownUri, local);
try {
await deserializer.readDriftElement(id);
} on CouldNotDeserializeException catch (e, s) {
backend.log.fine('Could not deserialize $id', e, s);
allRecovered = false;
}
}
return allRecovered;
}
Future<FileState> prepareFileForAnalysis(Uri uri,
{bool needsDiscovery = true}) async {
var known = cache.knownFiles[uri] ?? cache.notifyFileChanged(uri); var known = cache.knownFiles[uri] ?? cache.notifyFileChanged(uri);
if (known.discovery == null) { if (known.discovery == null && needsDiscovery) {
await DiscoverStep(this, known).discover(); await DiscoverStep(this, known).discover();
cache.postFileDiscoveryResults(known); cache.postFileDiscoveryResults(known);
@ -93,14 +135,30 @@ class DriftAnalysisDriver {
} }
} }
Future<FileState> fullyAnalyze(Uri uri) async { Future<FileState> resolveElements(Uri uri) async {
var known = cache.knownFiles[uri]; var known = cache.stateForUri(uri);
await prepareFileForAnalysis(uri, needsDiscovery: false);
if (known == null || known.discovery == null) { if (known.isFullyAnalyzed) {
known = await prepareFileForAnalysis(uri); // Well, there's nothing to do now.
return known;
} }
final allRecoveredFromCache = await _recoverFromCache(known);
if (allRecoveredFromCache) {
// We were able to read all elements from cache, so we don't have to
// run any analysis now.
return known;
}
// We couldn't recover all analyzed elements. Let's run an analysis run
// now then.
await prepareFileForAnalysis(uri, needsDiscovery: true);
await _analyzePrepared(known); await _analyzePrepared(known);
return known; return known;
} }
} }
abstract class AnalysisResultCacheReader {
Future<String?> readCacheFor(Uri uri);
}

View File

@ -17,6 +17,12 @@ class FileState {
String get extension => url.extension(ownUri.path); String get extension => url.extension(ownUri.path);
bool get isFullyAnalyzed {
return discovery != null &&
discovery!.locallyDefinedElements
.every((e) => elementIsAnalyzed(e.ownId));
}
bool elementIsAnalyzed(DriftElementId id) { bool elementIsAnalyzed(DriftElementId id) {
return analysis[id]?.isUpToDate == true; return analysis[id]?.isUpToDate == true;
} }

View File

@ -7,16 +7,20 @@ import 'package:collection/collection.dart';
import '../../driver/driver.dart'; import '../../driver/driver.dart';
class KnownDriftTypes { class KnownDriftTypes {
final LibraryElement helperLibrary;
final ClassElement tableElement; final ClassElement tableElement;
final InterfaceType tableType; final InterfaceType tableType;
final InterfaceType tableInfoType;
final InterfaceType driftDatabase; final InterfaceType driftDatabase;
final InterfaceType driftAccessor; final InterfaceType driftAccessor;
final InterfaceElement typeConverter; final InterfaceElement typeConverter;
final InterfaceElement jsonTypeConverter; final InterfaceElement jsonTypeConverter;
KnownDriftTypes._( KnownDriftTypes._(
this.helperLibrary,
this.tableElement, this.tableElement,
this.tableType, this.tableType,
this.tableInfoType,
this.typeConverter, this.typeConverter,
this.jsonTypeConverter, this.jsonTypeConverter,
this.driftDatabase, this.driftDatabase,
@ -32,8 +36,10 @@ class KnownDriftTypes {
final daoElement = exportNamespace.get('DriftAccessor') as ClassElement; final daoElement = exportNamespace.get('DriftAccessor') as ClassElement;
return KnownDriftTypes._( return KnownDriftTypes._(
helper,
tableElement, tableElement,
tableElement.defaultInstantiation, tableElement.defaultInstantiation,
(exportNamespace.get('TableInfo') as InterfaceElement).thisType,
exportNamespace.get('TypeConverter') as InterfaceElement, exportNamespace.get('TypeConverter') as InterfaceElement,
exportNamespace.get('JsonTypeConverter') as InterfaceElement, exportNamespace.get('JsonTypeConverter') as InterfaceElement,
dbElement.defaultInstantiation, dbElement.defaultInstantiation,

View File

@ -1,7 +1,8 @@
import 'dart:developer';
import 'package:analyzer/dart/ast/ast.dart' as dart; import 'package:analyzer/dart/ast/ast.dart' as dart;
import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart'; import 'package:analyzer/dart/element/visitor.dart';
import 'package:drift/drift.dart';
import 'package:recase/recase.dart'; import 'package:recase/recase.dart';
import 'package:source_gen/source_gen.dart'; import 'package:source_gen/source_gen.dart';
import 'package:sqlparser/sqlparser.dart' hide AnalysisError; import 'package:sqlparser/sqlparser.dart' hide AnalysisError;
@ -52,22 +53,24 @@ class DiscoverStep {
Future<void> discover() async { Future<void> discover() async {
final extension = _file.extension; final extension = _file.extension;
debugger(when: _file.ownUri.path.endsWith('todos.dart'));
switch (extension) { switch (extension) {
case '.dart': case '.dart':
LibraryElement library;
try { try {
final library = await _driver.backend.readDart(_file.ownUri); library = await _driver.backend.readDart(_file.ownUri);
final finder = } catch (e) {
_FindDartElements(this, library, await _driver.loadKnownTypes());
await finder.find();
_file.errorsDuringDiscovery.addAll(finder.errors);
_file.discovery =
DiscoveredDartLibrary(library, _checkForDuplicates(finder.found));
} catch (e, s) {
_driver.backend.log
.fine('Could not read Dart library from ${_file.ownUri}', e, s);
_file.discovery = NotADartLibrary(); _file.discovery = NotADartLibrary();
break;
} }
final finder =
_FindDartElements(this, library, await _driver.loadKnownTypes());
await finder.find();
_file.errorsDuringDiscovery.addAll(finder.errors);
_file.discovery =
DiscoveredDartLibrary(library, _checkForDuplicates(finder.found));
break; break;
case '.drift': case '.drift':
final engine = _driver.newSqlEngine(); final engine = _driver.newSqlEngine();
@ -141,7 +144,7 @@ class _FindDartElements extends RecursiveElementVisitor<void> {
final DiscoverStep _discoverStep; final DiscoverStep _discoverStep;
final LibraryElement _library; final LibraryElement _library;
final TypeChecker _isTable, _isDatabase, _isDao; final TypeChecker _isTable, _isTableInfo, _isDatabase, _isDao;
final List<Future<void>> _pendingWork = []; final List<Future<void>> _pendingWork = [];
@ -151,6 +154,7 @@ class _FindDartElements extends RecursiveElementVisitor<void> {
_FindDartElements( _FindDartElements(
this._discoverStep, this._library, KnownDriftTypes knownTypes) this._discoverStep, this._library, KnownDriftTypes knownTypes)
: _isTable = TypeChecker.fromStatic(knownTypes.tableType), : _isTable = TypeChecker.fromStatic(knownTypes.tableType),
_isTableInfo = TypeChecker.fromStatic(knownTypes.tableInfoType),
_isDatabase = TypeChecker.fromStatic(knownTypes.driftDatabase), _isDatabase = TypeChecker.fromStatic(knownTypes.driftDatabase),
_isDao = TypeChecker.fromStatic(knownTypes.driftAccessor); _isDao = TypeChecker.fromStatic(knownTypes.driftAccessor);
@ -159,9 +163,20 @@ class _FindDartElements extends RecursiveElementVisitor<void> {
await Future.wait(_pendingWork); await Future.wait(_pendingWork);
} }
bool _isDslTable(ClassElement element) {
// check if the table inherits from the drift table class. The !isExactly
// check is here because we run this generator on drift itself and we get
// weird errors for the Table class itself. In weird cases where we iterate
// over generated code (standalone tool), don't report existing
// implementations as tables.
return _isTable.isAssignableFrom(element) &&
!_isTable.isExactly(element) &&
!_isTableInfo.isAssignableFrom(element);
}
@override @override
void visitClassElement(ClassElement element) { void visitClassElement(ClassElement element) {
if (_isTable.isAssignableFrom(element)) { if (_isDslTable(element)) {
_pendingWork.add(Future.sync(() async { _pendingWork.add(Future.sync(() async {
final name = await _sqlNameOfTable(element); final name = await _sqlNameOfTable(element);
final id = _discoverStep._id(name); final id = _discoverStep._id(name);

View File

@ -41,7 +41,7 @@ class AnnotatedDartCode {
return AnnotatedDartCode([ return AnnotatedDartCode([
for (final part in serializedElements) for (final part in serializedElements)
if (part is Map) DartTopLevelSymbol.fromJson(json) else part as String if (part is Map) DartTopLevelSymbol.fromJson(part) else part as String
]); ]);
} }

View File

@ -1,15 +1,13 @@
import 'dart:convert' as convert;
import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/dart/element/type_system.dart';
import 'package:analyzer/dart/element/type_visitor.dart'; import 'package:analyzer/dart/element/type_visitor.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:drift/drift.dart' show DriftSqlType, UpdateKind; import 'package:drift/drift.dart' show DriftSqlType, UpdateKind;
import 'package:sqlparser/sqlparser.dart' show ReferenceAction; import 'package:sqlparser/sqlparser.dart' show ReferenceAction;
import 'driver/driver.dart';
import 'driver/state.dart';
import 'results/results.dart'; import 'results/results.dart';
class ElementSerializer { class ElementSerializer {
@ -260,38 +258,32 @@ class _DartTypeSerializer extends TypeVisitor<Map<String, Object?>> {
} }
} }
abstract class ElementDeserializer { class ElementDeserializer {
final Map<Uri, Map<String, Object?>> _loadedJson = {};
final Map<DriftElementId, DriftElement> _deserializedElements = {};
final Map<Uri, LibraryElement> _loadedLibraries = {}; final Map<Uri, LibraryElement> _loadedLibraries = {};
final TypeProvider defaultTypeProvider; final DriftAnalysisDriver driver;
final TypeSystem typeSystem;
ElementDeserializer(this.defaultTypeProvider, this.typeSystem); ElementDeserializer(this.driver);
/// Loads the serialized definitions of all elements with a
/// [DriftElementId.libraryUri] matching the [uri].
Future<String> loadStateForUri(Uri uri);
Future<LibraryElement> loadDartLibrary(Uri uri);
Future<DartType> _readDartType(Map json) async { Future<DartType> _readDartType(Map json) async {
final suffix = NullabilitySuffix.values.byName(json['suffix'] as String); final suffix = NullabilitySuffix.values.byName(json['suffix'] as String);
final helper = await driver.loadKnownTypes();
final typeProvider = helper.helperLibrary.typeProvider;
switch (json['kind'] as String) { switch (json['kind'] as String) {
case 'dynamic': case 'dynamic':
return defaultTypeProvider.dynamicType; return typeProvider.dynamicType;
case 'Never': case 'Never':
return suffix == NullabilitySuffix.none return suffix == NullabilitySuffix.none
? defaultTypeProvider.neverType ? typeProvider.neverType
: defaultTypeProvider.nullType; : typeProvider.nullType;
case 'void': case 'void':
return defaultTypeProvider.voidType; return typeProvider.voidType;
case 'interface': case 'interface':
final libraryUri = Uri.parse(json['library'] as String); final libraryUri = Uri.parse(json['library'] as String);
final lib = final lib = _loadedLibraries[libraryUri] ??=
_loadedLibraries[libraryUri] ??= await loadDartLibrary(libraryUri); await driver.backend.readDart(libraryUri);
final element = lib.exportNamespace.get(json['element'] as String) final element = lib.exportNamespace.get(json['element'] as String)
as InterfaceElement; as InterfaceElement;
final instantiation = [ final instantiation = [
@ -306,14 +298,33 @@ abstract class ElementDeserializer {
} }
} }
Future<DriftElement> _readElementReference(Map json) async { Future<DriftElement> _readElementReference(Map json) {
final id = DriftElementId.fromJson(json); return readDriftElement(DriftElementId.fromJson(json));
}
final data = _loadedJson[id.libraryUri] ??= convert.json Future<DriftElement> readDriftElement(DriftElementId id) async {
.decode(await loadStateForUri(id.libraryUri)) as Map<String, Object?>; final state = driver.cache.stateForUri(id.libraryUri).analysis[id] ??=
ElementAnalysisState(id);
if (state.result != null && state.isUpToDate) {
return state.result!;
}
return _deserializedElements[id] ??= final data = await driver.readStoredAnalysisResult(id.libraryUri);
await _readDriftElement(data[id.name] as Map); if (data == null) {
throw CouldNotDeserializeException(
'Analysis data for ${id..libraryUri} not found');
}
try {
final result = await _readDriftElement(data[id.name] as Map);
state
..result = result
..isUpToDate = true;
return result;
} catch (e, s) {
throw CouldNotDeserializeException(
'Internal error while deserializing $id: $e at \n$s');
}
} }
Future<DriftColumn> _readDriftColumnReference(Map json) async { Future<DriftColumn> _readDriftColumnReference(Map json) async {
@ -439,7 +450,54 @@ abstract class ElementDeserializer {
: null, : null,
source: source, source: source,
); );
case 'database':
case 'dao':
final referenceById = {
for (final reference in references) reference.id: reference,
};
final tables = [
for (final tableId in json['tables'])
referenceById[DriftElementId.fromJson(tableId as Map)] as DriftTable
];
final views = [
for (final tableId in json['views'])
referenceById[DriftElementId.fromJson(tableId as Map)] as DriftView
];
final includes =
(json['includes'] as List).cast<String>().map(Uri.parse).toList();
final queries = (json['views'] as List)
.cast<Map>()
.map(QueryOnAccessor.fromJson)
.toList();
if (type == 'database') {
return DriftDatabase(
id: id,
declaration: declaration,
declaredTables: tables,
declaredViews: views,
declaredIncludes: includes,
declaredQueries: queries,
schemaVersion: json['schema_version'] as int,
accessorTypes: [
for (final dao in json['daos'])
AnnotatedDartCode.fromJson(dao as Map)
],
);
} else {
assert(type == 'dao');
return DatabaseAccessor(
id: id,
declaration: declaration,
declaredTables: tables,
declaredViews: views,
declaredIncludes: includes,
declaredQueries: queries,
databaseClass: AnnotatedDartCode.fromJson(json['database'] as Map),
);
}
default: default:
throw UnimplementedError('Unsupported element type: $type'); throw UnimplementedError('Unsupported element type: $type');
} }
@ -512,3 +570,12 @@ abstract class ElementDeserializer {
} }
} }
} }
class CouldNotDeserializeException implements Exception {
final String message;
const CouldNotDeserializeException(this.message);
@override
String toString() => message;
}

View File

@ -5,7 +5,7 @@ import 'package:build/build.dart';
import '../../analysis/driver/driver.dart'; import '../../analysis/driver/driver.dart';
import '../../analysis/serializer.dart'; import '../../analysis/serializer.dart';
import '../../analyzer/options.dart'; import '../../analyzer/options.dart';
import 'new_backend.dart'; import 'backend.dart';
class DriftAnalyzer extends Builder { class DriftAnalyzer extends Builder {
final DriftOptions options; final DriftOptions options;
@ -24,13 +24,25 @@ class DriftAnalyzer extends Builder {
final backend = DriftBuildBackend(buildStep); final backend = DriftBuildBackend(buildStep);
final driver = DriftAnalysisDriver(backend, options); final driver = DriftAnalysisDriver(backend, options);
final results = await driver.fullyAnalyze(buildStep.inputId.uri); final results = await driver.resolveElements(buildStep.inputId.uri);
final serializer = ElementSerializer(); for (final parseError in results.errorsDuringDiscovery) {
final asJson = serializer.serializeElements( log.warning(parseError.toString());
results.analysis.values.map((e) => e.result).whereType()); }
final serialized = JsonUtf8Encoder(' ' * 2).convert(asJson);
await buildStep.writeAsBytes(buildStep.allowedOutputs.single, serialized); if (results.analysis.isNotEmpty) {
for (final result in results.analysis.values) {
for (final error in result.errorsDuringAnalysis) {
log.warning(error.toString());
}
}
final serializer = ElementSerializer();
final asJson = serializer.serializeElements(
results.analysis.values.map((e) => e.result).whereType());
final serialized = JsonUtf8Encoder(' ' * 2).convert(asJson);
await buildStep.writeAsBytes(buildStep.allowedOutputs.single, serialized);
}
} }
} }

View File

@ -5,6 +5,7 @@ import 'package:build/build.dart';
import 'package:build/build.dart' as build; import 'package:build/build.dart' as build;
import '../../analysis/backend.dart'; import '../../analysis/backend.dart';
import '../../analysis/driver/driver.dart';
class DriftBuildBackend extends DriftBackend { class DriftBuildBackend extends DriftBackend {
final BuildStep _buildStep; final BuildStep _buildStep;
@ -41,3 +42,20 @@ class DriftBuildBackend extends DriftBackend {
return _buildStep.resolver.astNodeFor(element, resolve: true); return _buildStep.resolver.astNodeFor(element, resolve: true);
} }
} }
class BuildCacheReader implements AnalysisResultCacheReader {
final BuildStep _buildStep;
BuildCacheReader(this._buildStep);
@override
Future<String?> readCacheFor(Uri uri) async {
// These files are generated by the `DriftAnalyzer` builder
final assetId = AssetId.resolve(uri).addExtension('.drift_module.json');
if (await _buildStep.canRead(assetId)) {
return _buildStep.readAsString(assetId);
}
return null;
}
}

View File

@ -1,108 +0,0 @@
import 'dart:convert';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart' hide log;
import 'package:build/build.dart' as build show log;
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:drift_dev/src/backends/backend.dart';
import 'package:logging/logging.dart';
import '../../analysis/preprocess_drift.dart';
class BuildBackend extends Backend {
final DriftOptions options;
BuildBackend([this.options = const DriftOptions.defaults()]);
BuildBackendTask createTask(BuildStep step) {
return BuildBackendTask(step, this);
}
@override
Uri resolve(Uri base, String import) {
final from = AssetId.resolve(base);
return AssetId.resolve(Uri.parse(import), from: from).uri;
}
}
class BuildBackendTask extends BackendTask {
final BuildStep step;
final BuildBackend backend;
BuildBackendTask(this.step, this.backend);
@override
Uri get entrypoint => step.inputId.uri;
AssetId _resolve(Uri uri) {
return AssetId.resolve(uri, from: step.inputId);
}
@override
Future<AstNode?> loadElementDeclaration(Element element) async {
return await step.resolver.astNodeFor(element, resolve: true);
}
@override
Future<String> readMoor(Uri uri) {
return step.readAsString(_resolve(uri));
}
@override
Future<LibraryElement> resolveDart(Uri uri) async {
try {
final asset = _resolve(uri);
return await step.resolver.libraryFor(asset);
} on NonLibraryAssetException catch (_) {
throw NotALibraryException(uri);
}
}
@override
Logger get log => build.log;
@override
Future<bool> exists(Uri uri) {
return step.canRead(_resolve(uri));
}
@override
Future<Expression> resolveExpression(
Uri context, String dartExpression, Iterable<String> imports) async {
// we try to detect all calls of resolveTypeOf in an earlier builder and
// prepare the result. See PreprocessBuilder for details
final preparedHelperFile =
_resolve(context).changeExtension('.drift_prep.json');
final temporaryFile = _resolve(context).changeExtension('.temp.dart');
if (!await step.canRead(preparedHelperFile)) {
throw CannotReadExpressionException('Generated helper file not found. '
'Check the build log for earlier errors.');
}
// todo: Cache this step?
final content = await step.readAsString(preparedHelperFile);
final json = DriftPreprocessorResult.fromJson(
jsonDecode(content) as Map<String, Object?>);
final fieldName = json.inlineDartExpressionsToHelperField[dartExpression];
if (fieldName == null) {
throw CannotReadExpressionException(
'Generated helper file does not contain '
'$dartExpression!');
}
final library = await step.resolver.libraryFor(temporaryFile);
final field = library.units.first.topLevelVariables
.firstWhere((element) => element.name == fieldName);
final fieldAst = await step.resolver.astNodeFor(field, resolve: true);
final initializer = (fieldAst as VariableDeclaration).initializer;
if (initializer == null) {
throw CannotReadExpressionException(
'Malformed helper file, this should never happen');
}
return initializer;
}
}

View File

@ -1,14 +1,8 @@
import 'package:build/build.dart'; import 'package:build/build.dart';
import 'package:drift_dev/src/analyzer/options.dart';
import 'package:drift_dev/src/analyzer/runner/file_graph.dart'; import '../../analysis/driver/driver.dart';
import 'package:drift_dev/src/analyzer/runner/results.dart'; import '../../analyzer/options.dart';
import 'package:drift_dev/src/analyzer/runner/task.dart'; import 'backend.dart';
import 'package:drift_dev/src/analyzer/session.dart';
import 'package:drift_dev/src/backends/build/build_backend.dart';
import 'package:drift_dev/src/backends/build/generators/dao_generator.dart';
import 'package:drift_dev/src/backends/build/generators/database_generator.dart';
import 'package:drift_dev/writer.dart';
import 'package:source_gen/source_gen.dart';
class _BuilderFlags { class _BuilderFlags {
bool didWarnAboutDeprecatedOptions = false; bool didWarnAboutDeprecatedOptions = false;
@ -16,68 +10,48 @@ class _BuilderFlags {
final _flags = Resource(() => _BuilderFlags()); final _flags = Resource(() => _BuilderFlags());
mixin DriftBuilder on Builder { enum DriftGenerationMode {
DriftOptions get options; /// Generate a shart part file which `source_gen:combining_builder` will then
/// pick up to generate a part for the input file.
///
/// Drift will generate a single part file for the main database file and each
/// DAO-defining file.
monolithicSharedPart,
Writer createWriter() { /// Like [monolithicSharedPart], except that drift will generate a single
return Writer(options); /// part file on its own instead of generating a part file for `source_gen`
} /// to process later.
monolithicPart,
Future<ParsedDartFile> analyzeDartFile(BuildStep step) async {
Task? task;
FoundFile input;
try {
final backend = BuildBackend(options);
final backendTask = backend.createTask(step);
final session = MoorSession(backend, options: options);
input = session.registerFile(step.inputId.uri);
task = session.startTask(backendTask);
await task.runTask();
} finally {
task?.printErrors();
}
return input.currentResult as ParsedDartFile;
}
} }
T _createBuilder<T extends DriftBuilder>( class DriftBuilder extends Builder {
BuilderOptions options,
T Function(List<Generator> generators, DriftOptions parsedOptions) creator,
) {
final parsedOptions = DriftOptions.fromJson(options.config);
final generators = <Generator>[
DriftDatabaseGenerator(),
DaoGenerator(),
];
final builder = creator(generators, parsedOptions);
for (final generator in generators.cast<BaseGenerator>()) {
generator.builder = builder;
}
return builder;
}
class DriftSharedPartBuilder extends SharedPartBuilder with DriftBuilder {
@override
final DriftOptions options; final DriftOptions options;
final DriftGenerationMode generationMode;
DriftSharedPartBuilder._( DriftBuilder._(this.options, this.generationMode);
List<Generator> generators, String name, this.options)
: super(generators, name);
factory DriftSharedPartBuilder(BuilderOptions options) { factory DriftBuilder(
return _createBuilder(options, (generators, parsedOptions) { DriftGenerationMode generationMode, BuilderOptions options) {
return DriftSharedPartBuilder._(generators, 'drift', parsedOptions); final parsedOptions = DriftOptions.fromJson(options.config);
}); return DriftBuilder._(parsedOptions, generationMode);
} }
@override @override
Future build(BuildStep buildStep) async { Map<String, List<String>> get buildExtensions {
switch (generationMode) {
case DriftGenerationMode.monolithicSharedPart:
return {
'.dart': ['.drift.g.part']
};
case DriftGenerationMode.monolithicPart:
return {
'.dart': ['.drift.dart']
};
}
}
@override
Future<void> build(BuildStep buildStep) async {
final flags = await buildStep.fetchResource(_flags); final flags = await buildStep.fetchResource(_flags);
if (!flags.didWarnAboutDeprecatedOptions && if (!flags.didWarnAboutDeprecatedOptions &&
options.enabledDeprecatedOption) { options.enabledDeprecatedOption) {
@ -87,24 +61,26 @@ class DriftSharedPartBuilder extends SharedPartBuilder with DriftBuilder {
flags.didWarnAboutDeprecatedOptions = true; flags.didWarnAboutDeprecatedOptions = true;
} }
return super.build(buildStep); final driver = DriftAnalysisDriver(DriftBuildBackend(buildStep), options)
..cacheReader = BuildCacheReader(buildStep);
final fromCache =
await driver.readStoredAnalysisResult(buildStep.inputId.uri);
if (fromCache == null) {
// Don't do anything! There are no analysis results for this file, so
// there's nothing for drift to generate code for.
return;
}
final result = await driver.resolveElements(buildStep.inputId.uri);
final buffer = StringBuffer();
for (final element in result.analysis.values) {
buffer.writeln('// ${element.ownId}');
}
await buildStep.writeAsString(
buildStep.allowedOutputs.single, buffer.toString());
} }
} }
class DriftPartBuilder extends PartBuilder with DriftBuilder {
@override
final DriftOptions options;
DriftPartBuilder._(List<Generator> generators, String extension, this.options)
: super(generators, extension);
factory DriftPartBuilder(BuilderOptions options) {
return _createBuilder(options, (generators, parsedOptions) {
return DriftPartBuilder._(generators, '.drift.dart', parsedOptions);
});
}
}
abstract class BaseGenerator {
late DriftBuilder builder;
}

View File

@ -1,43 +0,0 @@
import 'package:build/build.dart';
import 'package:drift_dev/src/backends/build/drift_builder.dart';
import 'package:drift_dev/src/utils/type_utils.dart';
import 'package:drift_dev/writer.dart';
import 'package:source_gen/source_gen.dart';
import '../../../model/base_entity.dart';
class DaoGenerator extends Generator implements BaseGenerator {
@override
late DriftBuilder builder;
@override
Future<String> generate(LibraryReader library, BuildStep buildStep) async {
final parsed = await builder.analyzeDartFile(buildStep);
final writer = builder.createWriter();
for (final dao in parsed.declaredDaos) {
final classScope = writer.child();
final element = dao.fromClass;
final daoName = element!.displayName;
final dbTypeName = dao.dbClass.codeString();
classScope.leaf().write('mixin _\$${daoName}Mixin on '
'DatabaseAccessor<$dbTypeName> {\n');
for (final entity in dao.entities.whereType<DriftEntityWithResultSet>()) {
final infoType = entity.entityInfoName;
final getterName = entity.dbGetterName;
classScope.leaf().write(
'$infoType get $getterName => attachedDatabase.$getterName;\n');
}
dao.queries
?.forEach((query) => QueryWriter(classScope.child()).write(query));
classScope.leaf().write('}');
}
return writer.writeGenerated();
}
}

View File

@ -1,46 +0,0 @@
import 'package:build/build.dart';
import 'package:drift_dev/src/backends/build/drift_builder.dart';
import 'package:drift_dev/writer.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:source_gen/source_gen.dart';
final _minLanguageVersion = Version(2, 12, 0);
class DriftDatabaseGenerator extends Generator implements BaseGenerator {
@override
late DriftBuilder builder;
@override
Future<String> generate(LibraryReader library, BuildStep buildStep) async {
final parsed = await builder.analyzeDartFile(buildStep);
final writer = builder.createWriter();
if (parsed.declaredDatabases.isNotEmpty) {
const ignore = '// ignore_for_file: type=lint';
writer.leaf().writeln(ignore);
}
for (final db in parsed.declaredDatabases) {
DatabaseWriter(db, writer.child()).write();
}
if (parsed.declaredDatabases.isNotEmpty) {
// Warn if the project uses an SDK version that is incompatible with what
// drift generates.
final version = library.element.languageVersion.effective;
final major = version.major;
final minor = version.minor;
if (version < _minLanguageVersion) {
log.warning(
'The language version of this file is Dart $major.$minor. '
'Drift generates code for Dart $_minLanguageVersion or later. Please '
'consider raising the minimum SDK version in your pubspec.yaml to at '
'least $_minLanguageVersion.',
);
}
}
return writer.writeGenerated();
}
}

View File

@ -3,7 +3,7 @@ import 'dart:convert';
import 'package:build/build.dart'; import 'package:build/build.dart';
import '../../analysis/preprocess_drift.dart'; import '../../analysis/preprocess_drift.dart';
import 'new_backend.dart'; import 'backend.dart';
/// A support builder that runs before the main generator to parse and resolve /// A support builder that runs before the main generator to parse and resolve
/// inline Dart resources in a `.drift` file. /// inline Dart resources in a `.drift` file.

View File

@ -17,8 +17,8 @@ class Foo extends Table {
''' '''
}); });
final file = final file = await backend.driver
await backend.driver.fullyAnalyze(Uri.parse('package:a/main.dart')); .resolveElements(Uri.parse('package:a/main.dart'));
expect(file.errorsDuringDiscovery, isEmpty); expect(file.errorsDuringDiscovery, isEmpty);
final result = file.analysis.values.single; final result = file.analysis.values.single;
@ -41,8 +41,8 @@ class Foo extends Table {
''' '''
}); });
final file = final file = await backend.driver
await backend.driver.fullyAnalyze(Uri.parse('package:a/main.dart')); .resolveElements(Uri.parse('package:a/main.dart'));
expect(file.errorsDuringDiscovery, isEmpty); expect(file.errorsDuringDiscovery, isEmpty);
final result = file.analysis.values.single; final result = file.analysis.values.single;
@ -68,8 +68,8 @@ class Foo extends Table {
''' '''
}); });
final file = final file = await backend.driver
await backend.driver.fullyAnalyze(Uri.parse('package:a/main.dart')); .resolveElements(Uri.parse('package:a/main.dart'));
expect(file.errorsDuringDiscovery, isEmpty); expect(file.errorsDuringDiscovery, isEmpty);
final result = file.analysis.values.single; final result = file.analysis.values.single;
@ -98,7 +98,7 @@ class Foo extends Table {
}); });
final uri = Uri.parse('package:a/main.dart'); final uri = Uri.parse('package:a/main.dart');
final file = await backend.driver.fullyAnalyze(uri); final file = await backend.driver.resolveElements(uri);
final otherTable = final otherTable =
file.analysis[DriftElementId(uri, 'other_table')]!.result as DriftTable; file.analysis[DriftElementId(uri, 'other_table')]!.result as DriftTable;
final foo = file.analysis[DriftElementId(uri, 'foo')]!.result as DriftTable; final foo = file.analysis[DriftElementId(uri, 'foo')]!.result as DriftTable;
@ -127,7 +127,7 @@ class Foo extends Table {
}); });
final file = final file =
await backend.driver.fullyAnalyze(Uri.parse('package:a/main.dart')); await backend.driver.resolveElements(Uri.parse('package:a/main.dart'));
final table = file.analysis.values.single.result as DriftTable; final table = file.analysis.values.single.result as DriftTable;
expect(table.references, isEmpty); expect(table.references, isEmpty);

View File

@ -102,7 +102,7 @@ class InvalidConstraints extends Table {
final uri = Uri.parse('package:a/main.dart'); final uri = Uri.parse('package:a/main.dart');
Future<ElementAnalysisState?> findTable(String dartName) async { Future<ElementAnalysisState?> findTable(String dartName) async {
final state = await backend.driver.fullyAnalyze(uri); final state = await backend.driver.resolveElements(uri);
return state.analysis.values.firstWhereOrNull((e) { return state.analysis.values.firstWhereOrNull((e) {
final result = e.result; final result = e.result;
@ -133,7 +133,7 @@ class InvalidConstraints extends Table {
}); });
test('reports discovery error for table with wrong name', () async { test('reports discovery error for table with wrong name', () async {
final state = await backend.driver.fullyAnalyze(uri); final state = await backend.driver.resolveElements(uri);
expect(state.errorsDuringDiscovery, [ expect(state.errorsDuringDiscovery, [
isDriftError( isDriftError(
contains('This getter must directly return a string literal'), contains('This getter must directly return a string literal'),

View File

@ -21,7 +21,7 @@ CREATE TABLE b (
}); });
final state = final state =
await backend.driver.fullyAnalyze(Uri.parse('package:a/a.drift')); await backend.driver.resolveElements(Uri.parse('package:a/a.drift'));
expect(state, hasNoErrors); expect(state, hasNoErrors);
final results = state.analysis.values.toList(); final results = state.analysis.values.toList();

View File

@ -20,7 +20,7 @@ CREATE TABLE b (
}); });
final state = final state =
await backend.driver.fullyAnalyze(Uri.parse('package:a/a.drift')); await backend.driver.resolveElements(Uri.parse('package:a/a.drift'));
expect(state, hasNoErrors); expect(state, hasNoErrors);
final results = state.analysis.values.toList(); final results = state.analysis.values.toList();
@ -51,7 +51,7 @@ CREATE TABLE a (
}); });
final state = final state =
await backend.driver.fullyAnalyze(Uri.parse('package:a/a.drift')); await backend.driver.resolveElements(Uri.parse('package:a/a.drift'));
expect(state, hasNoErrors); expect(state, hasNoErrors);
@ -77,7 +77,7 @@ CREATE TABLE b (
}); });
final stateA = final stateA =
await backend.driver.fullyAnalyze(Uri.parse('package:a/a.drift')); await backend.driver.resolveElements(Uri.parse('package:a/a.drift'));
expect(stateA, hasNoErrors); expect(stateA, hasNoErrors);
// Check that `b` has been analyzed and is in cache. // Check that `b` has been analyzed and is in cache.
@ -102,7 +102,7 @@ CREATE TABLE a (
}); });
final state = final state =
await backend.driver.fullyAnalyze(Uri.parse('package:a/a.drift')); await backend.driver.resolveElements(Uri.parse('package:a/a.drift'));
expect(state.errorsDuringDiscovery, isEmpty); expect(state.errorsDuringDiscovery, isEmpty);
final resultA = state.analysis.values.single; final resultA = state.analysis.values.single;