mirror of https://github.com/AMT-Cheif/drift.git
Start generating code from new analysis results
This commit is contained in:
parent
a6caae9d8f
commit
5947956109
|
@ -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;
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -2,12 +2,18 @@ import '../results/element.dart';
|
|||
import 'state.dart';
|
||||
|
||||
class DriftAnalysisCache {
|
||||
final Map<Uri, Map<String, Object?>> serializedElements = {};
|
||||
final Map<Uri, FileState> knownFiles = {};
|
||||
final Map<DriftElementId, DiscoveredElement> 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()
|
||||
|
|
|
@ -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<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);
|
||||
|
||||
if (known.discovery == null) {
|
||||
if (known.discovery == null && needsDiscovery) {
|
||||
await DiscoverStep(this, known).discover();
|
||||
cache.postFileDiscoveryResults(known);
|
||||
|
||||
|
@ -93,14 +135,30 @@ class DriftAnalysisDriver {
|
|||
}
|
||||
}
|
||||
|
||||
Future<FileState> fullyAnalyze(Uri uri) async {
|
||||
var known = cache.knownFiles[uri];
|
||||
Future<FileState> 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<String?> readCacheFor(Uri uri);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<void> 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<void> {
|
|||
final DiscoverStep _discoverStep;
|
||||
final LibraryElement _library;
|
||||
|
||||
final TypeChecker _isTable, _isDatabase, _isDao;
|
||||
final TypeChecker _isTable, _isTableInfo, _isDatabase, _isDao;
|
||||
|
||||
final List<Future<void>> _pendingWork = [];
|
||||
|
||||
|
@ -151,6 +154,7 @@ class _FindDartElements extends RecursiveElementVisitor<void> {
|
|||
_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<void> {
|
|||
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);
|
||||
|
|
|
@ -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
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Map<String, Object?>> {
|
|||
}
|
||||
}
|
||||
|
||||
abstract class ElementDeserializer {
|
||||
final Map<Uri, Map<String, Object?>> _loadedJson = {};
|
||||
final Map<DriftElementId, DriftElement> _deserializedElements = {};
|
||||
class ElementDeserializer {
|
||||
final Map<Uri, LibraryElement> _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<String> loadStateForUri(Uri uri);
|
||||
|
||||
Future<LibraryElement> loadDartLibrary(Uri uri);
|
||||
ElementDeserializer(this.driver);
|
||||
|
||||
Future<DartType> _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<DriftElement> _readElementReference(Map json) async {
|
||||
final id = DriftElementId.fromJson(json);
|
||||
Future<DriftElement> _readElementReference(Map json) {
|
||||
return readDriftElement(DriftElementId.fromJson(json));
|
||||
}
|
||||
|
||||
final data = _loadedJson[id.libraryUri] ??= convert.json
|
||||
.decode(await loadStateForUri(id.libraryUri)) as Map<String, Object?>;
|
||||
Future<DriftElement> 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<DriftColumn> _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<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:
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<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;
|
||||
}
|
||||
/// 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<T extends DriftBuilder>(
|
||||
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
|
||||
class DriftBuilder extends Builder {
|
||||
final DriftOptions options;
|
||||
final DriftGenerationMode generationMode;
|
||||
|
||||
DriftSharedPartBuilder._(
|
||||
List<Generator> 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<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);
|
||||
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<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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -102,7 +102,7 @@ class InvalidConstraints extends Table {
|
|||
final uri = Uri.parse('package:a/main.dart');
|
||||
|
||||
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) {
|
||||
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'),
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue