diff --git a/drift/lib/src/drift_dev_helper.dart b/drift/lib/src/drift_dev_helper.dart index 619e6fa2..7b86dced 100644 --- a/drift/lib/src/drift_dev_helper.dart +++ b/drift/lib/src/drift_dev_helper.dart @@ -1,4 +1,5 @@ // This field is analyzed by drift_dev to easily obtain common types. 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; diff --git a/drift_dev/lib/integrations/build.dart b/drift_dev/lib/integrations/build.dart index a4a94255..27bacdba 100644 --- a/drift_dev/lib/integrations/build.dart +++ b/drift_dev/lib/integrations/build.dart @@ -7,10 +7,11 @@ Builder preparingBuilder(BuilderOptions options) => PreprocessBuilder(); Builder analyzer(BuilderOptions options) => DriftAnalyzer(options); -Builder driftBuilder(BuilderOptions options) => DriftSharedPartBuilder(options); +Builder driftBuilder(BuilderOptions options) => + DriftBuilder(DriftGenerationMode.monolithicSharedPart, options); Builder driftBuilderNotShared(BuilderOptions options) => - DriftPartBuilder(options); + DriftBuilder(DriftGenerationMode.monolithicPart, options); PostProcessBuilder driftCleanup(BuilderOptions options) { return const FileDeletingBuilder(['.temp.dart']); diff --git a/drift_dev/lib/src/analysis/driver/cache.dart b/drift_dev/lib/src/analysis/driver/cache.dart index 05ff116a..c9bb1e75 100644 --- a/drift_dev/lib/src/analysis/driver/cache.dart +++ b/drift_dev/lib/src/analysis/driver/cache.dart @@ -2,12 +2,18 @@ import '../results/element.dart'; import 'state.dart'; class DriftAnalysisCache { + final Map> serializedElements = {}; final Map knownFiles = {}; final Map discoveredElements = {}; + FileState stateForUri(Uri uri) { + return knownFiles[uri] ?? notifyFileChanged(uri); + } + 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); return knownFiles.putIfAbsent(uri, () => FileState(uri)) ..errorsDuringDiscovery.clear() diff --git a/drift_dev/lib/src/analysis/driver/driver.dart b/drift_dev/lib/src/analysis/driver/driver.dart index 6762df1a..7dcde19a 100644 --- a/drift_dev/lib/src/analysis/driver/driver.dart +++ b/drift_dev/lib/src/analysis/driver/driver.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:sqlparser/sqlparser.dart'; import '../../analyzer/options.dart'; @@ -7,6 +9,8 @@ import '../resolver/dart/helper.dart'; import '../resolver/discover.dart'; import '../resolver/drift/sqlparser/mapping.dart'; import '../resolver/resolver.dart'; +import '../results/results.dart'; +import '../serializer.dart'; import 'cache.dart'; import 'error.dart'; import 'state.dart'; @@ -17,6 +21,9 @@ class DriftAnalysisDriver { final DriftOptions options; late final TypeMapping typeMapping = TypeMapping(options); + late final ElementDeserializer deserializer = ElementDeserializer(this); + + AnalysisResultCacheReader? cacheReader; KnownDriftTypes? _knownTypes; @@ -43,10 +50,45 @@ class DriftAnalysisDriver { return _knownTypes ??= await KnownDriftTypes.resolve(this); } - Future prepareFileForAnalysis(Uri uri) async { + Future?> 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; + return cache.serializedElements[uri] = parsed; + } + + Future _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 prepareFileForAnalysis(Uri uri, + {bool needsDiscovery = true}) async { var known = cache.knownFiles[uri] ?? cache.notifyFileChanged(uri); - if (known.discovery == null) { + if (known.discovery == null && needsDiscovery) { await DiscoverStep(this, known).discover(); cache.postFileDiscoveryResults(known); @@ -93,14 +135,30 @@ class DriftAnalysisDriver { } } - Future fullyAnalyze(Uri uri) async { - var known = cache.knownFiles[uri]; + Future resolveElements(Uri uri) async { + var known = cache.stateForUri(uri); + await prepareFileForAnalysis(uri, needsDiscovery: false); - if (known == null || known.discovery == null) { - known = await prepareFileForAnalysis(uri); + if (known.isFullyAnalyzed) { + // 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); return known; } } + +abstract class AnalysisResultCacheReader { + Future readCacheFor(Uri uri); +} diff --git a/drift_dev/lib/src/analysis/driver/state.dart b/drift_dev/lib/src/analysis/driver/state.dart index 0ab77eae..b9d1bc3b 100644 --- a/drift_dev/lib/src/analysis/driver/state.dart +++ b/drift_dev/lib/src/analysis/driver/state.dart @@ -17,6 +17,12 @@ class FileState { String get extension => url.extension(ownUri.path); + bool get isFullyAnalyzed { + return discovery != null && + discovery!.locallyDefinedElements + .every((e) => elementIsAnalyzed(e.ownId)); + } + bool elementIsAnalyzed(DriftElementId id) { return analysis[id]?.isUpToDate == true; } diff --git a/drift_dev/lib/src/analysis/resolver/dart/helper.dart b/drift_dev/lib/src/analysis/resolver/dart/helper.dart index ba37d00f..d5f8110d 100644 --- a/drift_dev/lib/src/analysis/resolver/dart/helper.dart +++ b/drift_dev/lib/src/analysis/resolver/dart/helper.dart @@ -7,16 +7,20 @@ import 'package:collection/collection.dart'; import '../../driver/driver.dart'; class KnownDriftTypes { + final LibraryElement helperLibrary; final ClassElement tableElement; final InterfaceType tableType; + final InterfaceType tableInfoType; final InterfaceType driftDatabase; final InterfaceType driftAccessor; final InterfaceElement typeConverter; final InterfaceElement jsonTypeConverter; KnownDriftTypes._( + this.helperLibrary, this.tableElement, this.tableType, + this.tableInfoType, this.typeConverter, this.jsonTypeConverter, this.driftDatabase, @@ -32,8 +36,10 @@ class KnownDriftTypes { final daoElement = exportNamespace.get('DriftAccessor') as ClassElement; return KnownDriftTypes._( + helper, tableElement, tableElement.defaultInstantiation, + (exportNamespace.get('TableInfo') as InterfaceElement).thisType, exportNamespace.get('TypeConverter') as InterfaceElement, exportNamespace.get('JsonTypeConverter') as InterfaceElement, dbElement.defaultInstantiation, diff --git a/drift_dev/lib/src/analysis/resolver/discover.dart b/drift_dev/lib/src/analysis/resolver/discover.dart index 91c2a380..ef3bda3c 100644 --- a/drift_dev/lib/src/analysis/resolver/discover.dart +++ b/drift_dev/lib/src/analysis/resolver/discover.dart @@ -1,7 +1,8 @@ +import 'dart:developer'; + import 'package:analyzer/dart/ast/ast.dart' as dart; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/visitor.dart'; -import 'package:drift/drift.dart'; import 'package:recase/recase.dart'; import 'package:source_gen/source_gen.dart'; import 'package:sqlparser/sqlparser.dart' hide AnalysisError; @@ -52,22 +53,24 @@ class DiscoverStep { Future discover() async { final extension = _file.extension; + debugger(when: _file.ownUri.path.endsWith('todos.dart')); + switch (extension) { case '.dart': + LibraryElement library; try { - final library = await _driver.backend.readDart(_file.ownUri); - final finder = - _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); + library = await _driver.backend.readDart(_file.ownUri); + } catch (e) { _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; case '.drift': final engine = _driver.newSqlEngine(); @@ -141,7 +144,7 @@ class _FindDartElements extends RecursiveElementVisitor { final DiscoverStep _discoverStep; final LibraryElement _library; - final TypeChecker _isTable, _isDatabase, _isDao; + final TypeChecker _isTable, _isTableInfo, _isDatabase, _isDao; final List> _pendingWork = []; @@ -151,6 +154,7 @@ class _FindDartElements extends RecursiveElementVisitor { _FindDartElements( this._discoverStep, this._library, KnownDriftTypes knownTypes) : _isTable = TypeChecker.fromStatic(knownTypes.tableType), + _isTableInfo = TypeChecker.fromStatic(knownTypes.tableInfoType), _isDatabase = TypeChecker.fromStatic(knownTypes.driftDatabase), _isDao = TypeChecker.fromStatic(knownTypes.driftAccessor); @@ -159,9 +163,20 @@ class _FindDartElements extends RecursiveElementVisitor { 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 void visitClassElement(ClassElement element) { - if (_isTable.isAssignableFrom(element)) { + if (_isDslTable(element)) { _pendingWork.add(Future.sync(() async { final name = await _sqlNameOfTable(element); final id = _discoverStep._id(name); diff --git a/drift_dev/lib/src/analysis/results/dart.dart b/drift_dev/lib/src/analysis/results/dart.dart index 1cd31b77..60a2aa31 100644 --- a/drift_dev/lib/src/analysis/results/dart.dart +++ b/drift_dev/lib/src/analysis/results/dart.dart @@ -41,7 +41,7 @@ class AnnotatedDartCode { return AnnotatedDartCode([ 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 ]); } diff --git a/drift_dev/lib/src/analysis/serializer.dart b/drift_dev/lib/src/analysis/serializer.dart index 33eb43a5..d0941144 100644 --- a/drift_dev/lib/src/analysis/serializer.dart +++ b/drift_dev/lib/src/analysis/serializer.dart @@ -1,15 +1,13 @@ -import 'dart:convert' as convert; - import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.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:collection/collection.dart'; import 'package:drift/drift.dart' show DriftSqlType, UpdateKind; import 'package:sqlparser/sqlparser.dart' show ReferenceAction; +import 'driver/driver.dart'; +import 'driver/state.dart'; import 'results/results.dart'; class ElementSerializer { @@ -260,38 +258,32 @@ class _DartTypeSerializer extends TypeVisitor> { } } -abstract class ElementDeserializer { - final Map> _loadedJson = {}; - final Map _deserializedElements = {}; +class ElementDeserializer { final Map _loadedLibraries = {}; - final TypeProvider defaultTypeProvider; - final TypeSystem typeSystem; + final DriftAnalysisDriver driver; - ElementDeserializer(this.defaultTypeProvider, this.typeSystem); - - /// Loads the serialized definitions of all elements with a - /// [DriftElementId.libraryUri] matching the [uri]. - Future loadStateForUri(Uri uri); - - Future loadDartLibrary(Uri uri); + ElementDeserializer(this.driver); Future _readDartType(Map json) async { final suffix = NullabilitySuffix.values.byName(json['suffix'] as String); + final helper = await driver.loadKnownTypes(); + + final typeProvider = helper.helperLibrary.typeProvider; switch (json['kind'] as String) { case 'dynamic': - return defaultTypeProvider.dynamicType; + return typeProvider.dynamicType; case 'Never': return suffix == NullabilitySuffix.none - ? defaultTypeProvider.neverType - : defaultTypeProvider.nullType; + ? typeProvider.neverType + : typeProvider.nullType; case 'void': - return defaultTypeProvider.voidType; + return typeProvider.voidType; case 'interface': final libraryUri = Uri.parse(json['library'] as String); - final lib = - _loadedLibraries[libraryUri] ??= await loadDartLibrary(libraryUri); + final lib = _loadedLibraries[libraryUri] ??= + await driver.backend.readDart(libraryUri); final element = lib.exportNamespace.get(json['element'] as String) as InterfaceElement; final instantiation = [ @@ -306,14 +298,33 @@ abstract class ElementDeserializer { } } - Future _readElementReference(Map json) async { - final id = DriftElementId.fromJson(json); + Future _readElementReference(Map json) { + return readDriftElement(DriftElementId.fromJson(json)); + } - final data = _loadedJson[id.libraryUri] ??= convert.json - .decode(await loadStateForUri(id.libraryUri)) as Map; + Future readDriftElement(DriftElementId id) async { + final state = driver.cache.stateForUri(id.libraryUri).analysis[id] ??= + ElementAnalysisState(id); + if (state.result != null && state.isUpToDate) { + return state.result!; + } - return _deserializedElements[id] ??= - await _readDriftElement(data[id.name] as Map); + final data = await driver.readStoredAnalysisResult(id.libraryUri); + 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 _readDriftColumnReference(Map json) async { @@ -439,7 +450,54 @@ abstract class ElementDeserializer { : null, 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().map(Uri.parse).toList(); + final queries = (json['views'] as List) + .cast() + .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: 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; +} diff --git a/drift_dev/lib/src/backends/build/analyzer.dart b/drift_dev/lib/src/backends/build/analyzer.dart index bf1c1228..87022796 100644 --- a/drift_dev/lib/src/backends/build/analyzer.dart +++ b/drift_dev/lib/src/backends/build/analyzer.dart @@ -5,7 +5,7 @@ import 'package:build/build.dart'; import '../../analysis/driver/driver.dart'; import '../../analysis/serializer.dart'; import '../../analyzer/options.dart'; -import 'new_backend.dart'; +import 'backend.dart'; class DriftAnalyzer extends Builder { final DriftOptions options; @@ -24,13 +24,25 @@ class DriftAnalyzer extends Builder { final backend = DriftBuildBackend(buildStep); final driver = DriftAnalysisDriver(backend, options); - final results = await driver.fullyAnalyze(buildStep.inputId.uri); + final results = await driver.resolveElements(buildStep.inputId.uri); - final serializer = ElementSerializer(); - final asJson = serializer.serializeElements( - results.analysis.values.map((e) => e.result).whereType()); - final serialized = JsonUtf8Encoder(' ' * 2).convert(asJson); + for (final parseError in results.errorsDuringDiscovery) { + log.warning(parseError.toString()); + } - 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); + } } } diff --git a/drift_dev/lib/src/backends/build/new_backend.dart b/drift_dev/lib/src/backends/build/backend.dart similarity index 69% rename from drift_dev/lib/src/backends/build/new_backend.dart rename to drift_dev/lib/src/backends/build/backend.dart index d142c178..5e7fa808 100644 --- a/drift_dev/lib/src/backends/build/new_backend.dart +++ b/drift_dev/lib/src/backends/build/backend.dart @@ -5,6 +5,7 @@ import 'package:build/build.dart'; import 'package:build/build.dart' as build; import '../../analysis/backend.dart'; +import '../../analysis/driver/driver.dart'; class DriftBuildBackend extends DriftBackend { final BuildStep _buildStep; @@ -41,3 +42,20 @@ class DriftBuildBackend extends DriftBackend { return _buildStep.resolver.astNodeFor(element, resolve: true); } } + +class BuildCacheReader implements AnalysisResultCacheReader { + final BuildStep _buildStep; + + BuildCacheReader(this._buildStep); + + @override + Future 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; + } +} diff --git a/drift_dev/lib/src/backends/build/build_backend.dart b/drift_dev/lib/src/backends/build/build_backend.dart deleted file mode 100644 index 5ab3f898..00000000 --- a/drift_dev/lib/src/backends/build/build_backend.dart +++ /dev/null @@ -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 loadElementDeclaration(Element element) async { - return await step.resolver.astNodeFor(element, resolve: true); - } - - @override - Future readMoor(Uri uri) { - return step.readAsString(_resolve(uri)); - } - - @override - Future 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 exists(Uri uri) { - return step.canRead(_resolve(uri)); - } - - @override - Future resolveExpression( - Uri context, String dartExpression, Iterable 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); - - 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; - } -} diff --git a/drift_dev/lib/src/backends/build/drift_builder.dart b/drift_dev/lib/src/backends/build/drift_builder.dart index 528f1a32..e48b5af6 100644 --- a/drift_dev/lib/src/backends/build/drift_builder.dart +++ b/drift_dev/lib/src/backends/build/drift_builder.dart @@ -1,14 +1,8 @@ import 'package:build/build.dart'; -import 'package:drift_dev/src/analyzer/options.dart'; -import 'package:drift_dev/src/analyzer/runner/file_graph.dart'; -import 'package:drift_dev/src/analyzer/runner/results.dart'; -import 'package:drift_dev/src/analyzer/runner/task.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'; + +import '../../analysis/driver/driver.dart'; +import '../../analyzer/options.dart'; +import 'backend.dart'; class _BuilderFlags { bool didWarnAboutDeprecatedOptions = false; @@ -16,68 +10,48 @@ class _BuilderFlags { final _flags = Resource(() => _BuilderFlags()); -mixin DriftBuilder on Builder { - DriftOptions get options; +enum DriftGenerationMode { + /// 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() { - return Writer(options); - } - - Future 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; - } + /// Like [monolithicSharedPart], except that drift will generate a single + /// part file on its own instead of generating a part file for `source_gen` + /// to process later. + monolithicPart, } -T _createBuilder( - BuilderOptions options, - T Function(List generators, DriftOptions parsedOptions) creator, -) { - final parsedOptions = DriftOptions.fromJson(options.config); - - final generators = [ - DriftDatabaseGenerator(), - DaoGenerator(), - ]; - - final builder = creator(generators, parsedOptions); - - for (final generator in generators.cast()) { - generator.builder = builder; - } - - return builder; -} - -class DriftSharedPartBuilder extends SharedPartBuilder with DriftBuilder { - @override +class DriftBuilder extends Builder { final DriftOptions options; + final DriftGenerationMode generationMode; - DriftSharedPartBuilder._( - List generators, String name, this.options) - : super(generators, name); + DriftBuilder._(this.options, this.generationMode); - factory DriftSharedPartBuilder(BuilderOptions options) { - return _createBuilder(options, (generators, parsedOptions) { - return DriftSharedPartBuilder._(generators, 'drift', parsedOptions); - }); + factory DriftBuilder( + DriftGenerationMode generationMode, BuilderOptions options) { + final parsedOptions = DriftOptions.fromJson(options.config); + return DriftBuilder._(parsedOptions, generationMode); } @override - Future build(BuildStep buildStep) async { + Map> get buildExtensions { + switch (generationMode) { + case DriftGenerationMode.monolithicSharedPart: + return { + '.dart': ['.drift.g.part'] + }; + case DriftGenerationMode.monolithicPart: + return { + '.dart': ['.drift.dart'] + }; + } + } + + @override + Future build(BuildStep buildStep) async { final flags = await buildStep.fetchResource(_flags); if (!flags.didWarnAboutDeprecatedOptions && options.enabledDeprecatedOption) { @@ -87,24 +61,26 @@ class DriftSharedPartBuilder extends SharedPartBuilder with DriftBuilder { 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 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; -} diff --git a/drift_dev/lib/src/backends/build/generators/dao_generator.dart b/drift_dev/lib/src/backends/build/generators/dao_generator.dart deleted file mode 100644 index 6c01d742..00000000 --- a/drift_dev/lib/src/backends/build/generators/dao_generator.dart +++ /dev/null @@ -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 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()) { - 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(); - } -} diff --git a/drift_dev/lib/src/backends/build/generators/database_generator.dart b/drift_dev/lib/src/backends/build/generators/database_generator.dart deleted file mode 100644 index 4a232a0b..00000000 --- a/drift_dev/lib/src/backends/build/generators/database_generator.dart +++ /dev/null @@ -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 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(); - } -} diff --git a/drift_dev/lib/src/backends/build/preprocess_builder.dart b/drift_dev/lib/src/backends/build/preprocess_builder.dart index 7bb9af2c..cd76ea36 100644 --- a/drift_dev/lib/src/backends/build/preprocess_builder.dart +++ b/drift_dev/lib/src/backends/build/preprocess_builder.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:build/build.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 /// inline Dart resources in a `.drift` file. diff --git a/drift_dev/test/analysis/resolver/dart/foreign_key_test.dart b/drift_dev/test/analysis/resolver/dart/foreign_key_test.dart index 02108b48..c687327c 100644 --- a/drift_dev/test/analysis/resolver/dart/foreign_key_test.dart +++ b/drift_dev/test/analysis/resolver/dart/foreign_key_test.dart @@ -17,8 +17,8 @@ class Foo extends Table { ''' }); - final file = - await backend.driver.fullyAnalyze(Uri.parse('package:a/main.dart')); + final file = await backend.driver + .resolveElements(Uri.parse('package:a/main.dart')); expect(file.errorsDuringDiscovery, isEmpty); final result = file.analysis.values.single; @@ -41,8 +41,8 @@ class Foo extends Table { ''' }); - final file = - await backend.driver.fullyAnalyze(Uri.parse('package:a/main.dart')); + final file = await backend.driver + .resolveElements(Uri.parse('package:a/main.dart')); expect(file.errorsDuringDiscovery, isEmpty); final result = file.analysis.values.single; @@ -68,8 +68,8 @@ class Foo extends Table { ''' }); - final file = - await backend.driver.fullyAnalyze(Uri.parse('package:a/main.dart')); + final file = await backend.driver + .resolveElements(Uri.parse('package:a/main.dart')); expect(file.errorsDuringDiscovery, isEmpty); final result = file.analysis.values.single; @@ -98,7 +98,7 @@ class Foo extends Table { }); final uri = Uri.parse('package:a/main.dart'); - final file = await backend.driver.fullyAnalyze(uri); + final file = await backend.driver.resolveElements(uri); final otherTable = file.analysis[DriftElementId(uri, 'other_table')]!.result as DriftTable; final foo = file.analysis[DriftElementId(uri, 'foo')]!.result as DriftTable; @@ -127,7 +127,7 @@ class Foo extends Table { }); 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; expect(table.references, isEmpty); diff --git a/drift_dev/test/analysis/resolver/dart/table_test.dart b/drift_dev/test/analysis/resolver/dart/table_test.dart index 4306537f..a3ae140f 100644 --- a/drift_dev/test/analysis/resolver/dart/table_test.dart +++ b/drift_dev/test/analysis/resolver/dart/table_test.dart @@ -102,7 +102,7 @@ class InvalidConstraints extends Table { final uri = Uri.parse('package:a/main.dart'); Future findTable(String dartName) async { - final state = await backend.driver.fullyAnalyze(uri); + final state = await backend.driver.resolveElements(uri); return state.analysis.values.firstWhereOrNull((e) { final result = e.result; @@ -133,7 +133,7 @@ class InvalidConstraints extends Table { }); 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, [ isDriftError( contains('This getter must directly return a string literal'), diff --git a/drift_dev/test/analysis/resolver/drift/table_test.dart b/drift_dev/test/analysis/resolver/drift/table_test.dart index 91eeaa1f..cc4ba7bb 100644 --- a/drift_dev/test/analysis/resolver/drift/table_test.dart +++ b/drift_dev/test/analysis/resolver/drift/table_test.dart @@ -21,7 +21,7 @@ CREATE TABLE b ( }); 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); final results = state.analysis.values.toList(); diff --git a/drift_dev/test/analysis/resolver/resolver_test.dart b/drift_dev/test/analysis/resolver/resolver_test.dart index d3fbfeef..19e8e364 100644 --- a/drift_dev/test/analysis/resolver/resolver_test.dart +++ b/drift_dev/test/analysis/resolver/resolver_test.dart @@ -20,7 +20,7 @@ CREATE TABLE b ( }); 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); final results = state.analysis.values.toList(); @@ -51,7 +51,7 @@ CREATE TABLE a ( }); 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); @@ -77,7 +77,7 @@ CREATE TABLE b ( }); 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); // Check that `b` has been analyzed and is in cache. @@ -102,7 +102,7 @@ CREATE TABLE a ( }); 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); final resultA = state.analysis.values.single;