Avoid resolving all transitive imports in build

This commit is contained in:
Simon Binder 2023-08-12 14:35:16 +02:00
parent f857cb17b5
commit 788420b614
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
23 changed files with 342 additions and 113 deletions

View File

@ -23,7 +23,7 @@ builders:
# Regular build flow, emitting a shared part file for source_gen to pick up # Regular build flow, emitting a shared part file for source_gen to pick up
drift_dev: drift_dev:
import: "package:drift_dev/integrations/build.dart" import: "package:drift_dev/integrations/build.dart"
builder_factories: ["analyzer", "driftBuilder"] builder_factories: ["discover", "analyzer", "driftBuilder"]
build_extensions: build_extensions:
".dart": [".drift.g.part", ".dart.drift_module.json"] ".dart": [".drift.g.part", ".dart.drift_module.json"]
".drift": [".drift.drift_module.json"] ".drift": [".drift.drift_module.json"]
@ -54,7 +54,7 @@ builders:
applies_builders: [":analyzer"] applies_builders: [":analyzer"]
analyzer: analyzer:
import: "package:drift_dev/integrations/build.dart" import: "package:drift_dev/integrations/build.dart"
builder_factories: ["analyzer"] builder_factories: ["discover", "analyzer"]
build_extensions: build_extensions:
".dart": [".dart.drift_module.json"] ".dart": [".dart.drift_module.json"]
".drift": [".drift.drift_module.json"] ".drift": [".drift.drift_module.json"]

View File

@ -5,6 +5,8 @@ import 'package:drift_dev/src/backends/build/preprocess_builder.dart';
Builder preparingBuilder(BuilderOptions options) => PreprocessBuilder(); Builder preparingBuilder(BuilderOptions options) => PreprocessBuilder();
Builder discover(BuilderOptions options) => DriftDiscover(options);
Builder analyzer(BuilderOptions options) => DriftAnalyzer(options); Builder analyzer(BuilderOptions options) => DriftAnalyzer(options);
Builder driftBuilder(BuilderOptions options) => Builder driftBuilder(BuilderOptions options) =>

View File

@ -7,7 +7,7 @@ import 'state.dart';
class DriftAnalysisCache { class DriftAnalysisCache {
final Map<Uri, CachedSerializationResult> serializationCache = {}; final Map<Uri, CachedSerializationResult> serializationCache = {};
final Map<Uri, FileState> knownFiles = {}; final Map<Uri, FileState> knownFiles = {};
final Map<DriftElementId, DiscoveredElement> discoveredElements = {}; final Map<DriftElementId, DriftElementKind> discoveredElements = {};
FileState stateForUri(Uri uri) { FileState stateForUri(Uri uri) {
return knownFiles[uri] ?? notifyFileChanged(uri); return knownFiles[uri] ?? notifyFileChanged(uri);
@ -26,15 +26,11 @@ class DriftAnalysisCache {
void notifyFileDeleted(Uri uri) {} void notifyFileDeleted(Uri uri) {}
void postFileDiscoveryResults(FileState state) { void knowsLocalElements(FileState state) {
discoveredElements.removeWhere((key, _) => key.libraryUri == state.ownUri); discoveredElements.removeWhere((key, _) => key.libraryUri == state.ownUri);
final discovery = state.discovery; for (final (id, kind) in state.definedElements) {
if (discovery != null) { discoveredElements[id] = kind;
discoveredElements.addAll({
for (final definedHere in discovery.locallyDefinedElements)
definedHere.ownId: definedHere,
});
} }
} }

View File

@ -107,7 +107,7 @@ class DriftAnalysisDriver {
final reader = cacheReader; final reader = cacheReader;
if (reader == null) return null; if (reader == null) return null;
final found = await reader.readCacheFor(uri); final found = await reader.readElementCacheFor(uri);
if (found == null) return null; if (found == null) return null;
final parsed = json.decode(found) as Map<String, Object?>; final parsed = json.decode(found) as Map<String, Object?>;
@ -140,7 +140,8 @@ class DriftAnalysisDriver {
final cachedImports = cache.serializationCache[state.ownUri]?.cachedImports; final cachedImports = cache.serializationCache[state.ownUri]?.cachedImports;
if (cachedImports != null && state.discovery == null) { if (cachedImports != null && state.discovery == null) {
state.cachedImports = cachedImports; state.cachedDiscovery ??= CachedDiscoveryResults(true, cachedImports, {});
for (final import in cachedImports) { for (final import in cachedImports) {
final found = cache.stateForUri(import); final found = cache.stateForUri(import);
@ -156,53 +157,93 @@ class DriftAnalysisDriver {
return allRecovered; return allRecovered;
} }
/// Runs the first step (element discovery) on a file with the given [uri]. Future<void> discoverIfNecessary(
Future<FileState> prepareFileForAnalysis( FileState file, {
Uri uri, {
bool needsDiscovery = true,
bool warnIfFileDoesntExist = true, bool warnIfFileDoesntExist = true,
}) async { }) async {
var known = cache.knownFiles[uri] ?? cache.notifyFileChanged(uri); if (file.discovery == null) {
await DiscoverStep(this, file)
if (known.discovery == null && needsDiscovery) {
await DiscoverStep(this, known)
.discover(warnIfFileDoesntExist: warnIfFileDoesntExist); .discover(warnIfFileDoesntExist: warnIfFileDoesntExist);
cache.postFileDiscoveryResults(known); cache.knowsLocalElements(file);
}
}
// todo: Mark elements that need to be analyzed again /// Runs the first step (discovering local elements) on a file with the given
/// [uri].
Future<FileState> findLocalElements(
Uri uri, {
bool warnIfFileDoesntExist = true,
}) async {
final known = cache.knownFiles[uri] ?? cache.notifyFileChanged(uri);
// To analyze a drift file, we also need to be able to analyze imports. if (known.cachedDiscovery != null || known.discovery != null) {
final state = known.discovery; // We already know local elements.
if (state is DiscoveredDriftFile) { return known;
for (final import in state.imports) { }
// todo: We shouldn't unconditionally crawl files like this. The build
// backend should emit prepared file results in a previous step which
// should be used here.
final file = await prepareFileForAnalysis(import.importedUri);
if (file.discovery?.isValidImport != true) { // First, try to read cached results.
known.errorsDuringDiscovery.add( final reader = cacheReader;
DriftAnalysisError.inDriftFile( CachedDiscoveryResults? cached;
import.ast,
'The imported file, `${import.importedUri}`, does not exist or ' if (reader != null) {
"can't be imported.", cached = await reader.readDiscovery(uri);
),
); if (cached == null && reader.findsLocalElementsReliably) {
} // There are no locally defined elements, since otherwise the reader
} // would have found them.
} else if (state is DiscoveredDartLibrary) { cached = CachedDiscoveryResults(false, const [], const {});
for (final import in state.importDependencies) {
// We might import a generated file that doesn't exist yet, that
// should not be a user-visible error. Users will notice because the
// import is reported as an error by the analyzer either way.
await prepareFileForAnalysis(import, warnIfFileDoesntExist: false);
}
} }
} }
if (cached != null) {
known.cachedDiscovery = cached;
cache.knowsLocalElements(known);
} else {
await discoverIfNecessary(
known,
warnIfFileDoesntExist: warnIfFileDoesntExist,
);
}
return known; return known;
} }
Future<void> _findLocalElementsInAllImports(FileState known) async {
// To analyze references in elements, we also need to know locally defined
// elements in all imports.
final state = known.discovery;
if (state is DiscoveredDriftFile) {
for (final import in state.imports) {
// todo: We shouldn't unconditionally crawl files like this. The build
// backend should emit prepared file results in a previous step which
// should be used here.
final file = await findLocalElements(import.importedUri);
if (file.isValidImport != true) {
known.errorsDuringDiscovery.add(
DriftAnalysisError.inDriftFile(
import.ast,
'The imported file, `${import.importedUri}`, does not exist or '
"can't be imported.",
),
);
} else {
await _findLocalElementsInAllImports(file);
}
}
} else {
for (final import in known.imports ?? const <Uri>[]) {
await findLocalElements(
import,
// We might import a generated file that doesn't exist yet, that
// should not be a user-visible error. Users will notice because the
// import is reported as an error by the analyzer either way.
warnIfFileDoesntExist: false,
);
}
}
}
/// Runs the second analysis step (element analysis) on a file. /// Runs the second analysis step (element analysis) on a file.
/// ///
/// The file, as well as all imports, should have undergone the first analysis /// The file, as well as all imports, should have undergone the first analysis
@ -232,8 +273,6 @@ class DriftAnalysisDriver {
/// necessary work up until that point. /// necessary work up until that point.
Future<FileState> resolveElements(Uri uri) async { Future<FileState> resolveElements(Uri uri) async {
var known = cache.stateForUri(uri); var known = cache.stateForUri(uri);
await prepareFileForAnalysis(uri, needsDiscovery: false);
if (known.isFullyAnalyzed) { if (known.isFullyAnalyzed) {
// Well, there's nothing to do now. // Well, there's nothing to do now.
return known; return known;
@ -247,8 +286,10 @@ class DriftAnalysisDriver {
} }
// We couldn't recover all analyzed elements. Let's run an analysis run // We couldn't recover all analyzed elements. Let's run an analysis run
// now then. // then.
await prepareFileForAnalysis(uri, needsDiscovery: true); await discoverIfNecessary(known);
await _findLocalElementsInAllImports(known);
await _analyzePrepared(known); await _analyzePrepared(known);
return known; return known;
} }
@ -299,8 +340,19 @@ class DriftAnalysisDriver {
/// This class is responsible for recovering both assets in a subsequent build- /// This class is responsible for recovering both assets in a subsequent build-
/// step. /// step.
abstract class AnalysisResultCacheReader { abstract class AnalysisResultCacheReader {
/// Whether [readDiscovery] only returns `null` when the file under the URI
/// is not relevant to drift.
bool get findsLocalElementsReliably;
/// Whether [readElementCacheFor] is guaranteed to return all elements defined
/// in the supplied `uri`, or whether it could be that we just didn't analyze
/// that file yet.
bool get findsResolvedElementsReliably;
Future<CachedDiscoveryResults?> readDiscovery(Uri uri);
Future<LibraryElement?> readTypeHelperFor(Uri uri); Future<LibraryElement?> readTypeHelperFor(Uri uri);
Future<String?> readCacheFor(Uri uri); Future<String?> readElementCacheFor(Uri uri);
} }
/// Thrown by a local element resolver when an element could not be resolved and /// Thrown by a local element resolver when an element could not be resolved and

View File

@ -15,7 +15,7 @@ class FileState {
final Uri ownUri; final Uri ownUri;
DiscoveredFileState? discovery; DiscoveredFileState? discovery;
List<Uri>? cachedImports; CachedDiscoveryResults? cachedDiscovery;
final List<DriftAnalysisError> errorsDuringDiscovery = []; final List<DriftAnalysisError> errorsDuringDiscovery = [];
@ -26,7 +26,12 @@ class FileState {
FileState(this.ownUri); FileState(this.ownUri);
Iterable<Uri>? get imports => discovery?.importDependencies ?? cachedImports; bool get isValidImport {
return (cachedDiscovery?.isValidImport ?? discovery?.isValidImport) == true;
}
Iterable<Uri>? get imports =>
discovery?.importDependencies ?? cachedDiscovery?.imports;
String get extension => url.extension(ownUri.path); String get extension => url.extension(ownUri.path);
@ -35,6 +40,22 @@ class FileState {
return analyzedElements.any((e) => e is BaseDriftAccessor); return analyzedElements.any((e) => e is BaseDriftAccessor);
} }
Iterable<(DriftElementId, DriftElementKind)> get definedElements sync* {
final discovery = this.discovery;
final cached = cachedDiscovery;
if (discovery != null) {
for (final element in discovery.locallyDefinedElements) {
yield (element.ownId, element.kind);
}
} else if (cached != null) {
for (final MapEntry(:key, :value)
in cached.locallyDefinedElements.entries) {
yield (id(key), value);
}
}
}
/// All analyzed [DriftElement]s found in this library. /// All analyzed [DriftElement]s found in this library.
@visibleForTesting @visibleForTesting
Iterable<DriftElement> get analyzedElements { Iterable<DriftElement> get analyzedElements {
@ -90,6 +111,18 @@ class FileState {
} }
} }
class CachedDiscoveryResults {
final bool isValidImport;
final List<Uri> imports;
final Map<String, DriftElementKind> locallyDefinedElements;
CachedDiscoveryResults(
this.isValidImport,
this.imports,
this.locallyDefinedElements,
);
}
abstract class DiscoveredFileState { abstract class DiscoveredFileState {
final List<DiscoveredElement> locallyDefinedElements; final List<DiscoveredElement> locallyDefinedElements;
@ -156,6 +189,7 @@ class UnknownFile extends DiscoveredFileState {
abstract class DiscoveredElement { abstract class DiscoveredElement {
final DriftElementId ownId; final DriftElementId ownId;
DriftElementKind get kind;
DiscoveredElement(this.ownId); DiscoveredElement(this.ownId);

View File

@ -91,9 +91,9 @@ class DartAccessorResolver
} else { } else {
includes.add(import); includes.add(import);
final resolved = await resolver.driver.prepareFileForAnalysis( final resolved = await resolver.driver
discovered.ownId.libraryUri.resolveUri(import)); .findLocalElements(discovered.ownId.libraryUri.resolveUri(import));
if (resolved.discovery?.isValidImport != true) { if (!resolved.isValidImport) {
reportError( reportError(
DriftAnalysisError.forDartElement( DriftAnalysisError.forDartElement(
element, '`$value` could not be imported'), element, '`$value` could not be imported'),

View File

@ -116,17 +116,17 @@ class DiscoverStep {
imports.add(DriftFileImport(node, uri)); imports.add(DriftFileImport(node, uri));
} else if (node is TableInducingStatement) { } else if (node is TableInducingStatement) {
pendingElements pendingElements.add(DiscoveredDriftTable(
.add(DiscoveredDriftTable(_id(node.createdName), node)); _id(node.createdName), DriftElementKind.table, node));
} else if (node is CreateViewStatement) { } else if (node is CreateViewStatement) {
pendingElements pendingElements.add(DiscoveredDriftView(
.add(DiscoveredDriftView(_id(node.createdName), node)); _id(node.createdName), DriftElementKind.view, node));
} else if (node is CreateIndexStatement) { } else if (node is CreateIndexStatement) {
pendingElements pendingElements.add(DiscoveredDriftIndex(
.add(DiscoveredDriftIndex(_id(node.indexName), node)); _id(node.indexName), DriftElementKind.dbIndex, node));
} else if (node is CreateTriggerStatement) { } else if (node is CreateTriggerStatement) {
pendingElements pendingElements.add(DiscoveredDriftTrigger(
.add(DiscoveredDriftTrigger(_id(node.triggerName), node)); _id(node.triggerName), DriftElementKind.trigger, node));
} else if (node is DeclaredStatement) { } else if (node is DeclaredStatement) {
String name; String name;
@ -137,7 +137,8 @@ class DiscoverStep {
name = '\$drift_${specialQueryNameCount++}'; name = '\$drift_${specialQueryNameCount++}';
} }
pendingElements.add(DiscoveredDriftStatement(_id(name), node)); pendingElements.add(DiscoveredDriftStatement(
_id(name), DriftElementKind.definedQuery, node));
} }
} }

View File

@ -4,11 +4,15 @@ import 'package:drift/drift.dart' show DriftView;
import 'package:sqlparser/sqlparser.dart'; import 'package:sqlparser/sqlparser.dart';
import '../driver/state.dart'; import '../driver/state.dart';
import '../results/element.dart';
class DiscoveredDriftElement<AST extends AstNode> extends DiscoveredElement { class DiscoveredDriftElement<AST extends AstNode> extends DiscoveredElement {
final AST sqlNode; final AST sqlNode;
DiscoveredDriftElement(super.ownId, this.sqlNode); @override
final DriftElementKind kind;
DiscoveredDriftElement(super.ownId, this.kind, this.sqlNode);
} }
typedef DiscoveredDriftTable = DiscoveredDriftElement<TableInducingStatement>; typedef DiscoveredDriftTable = DiscoveredDriftElement<TableInducingStatement>;
@ -25,6 +29,9 @@ abstract class DiscoveredDartElement<DE extends Element>
} }
class DiscoveredDartTable extends DiscoveredDartElement<ClassElement> { class DiscoveredDartTable extends DiscoveredDartElement<ClassElement> {
@override
DriftElementKind get kind => DriftElementKind.table;
DiscoveredDartTable(super.ownId, super.dartElement); DiscoveredDartTable(super.ownId, super.dartElement);
} }
@ -32,6 +39,9 @@ class DiscoveredDartView extends DiscoveredDartElement<ClassElement> {
/// The [DriftView] annotation on this class, if there is any. /// The [DriftView] annotation on this class, if there is any.
DartObject? viewAnnotation; DartObject? viewAnnotation;
@override
DriftElementKind get kind => DriftElementKind.view;
DiscoveredDartView(super.ownId, super.dartElement, this.viewAnnotation); DiscoveredDartView(super.ownId, super.dartElement, this.viewAnnotation);
} }
@ -39,6 +49,11 @@ class DiscoveredBaseAccessor extends DiscoveredDartElement<ClassElement> {
final bool isDatabase; final bool isDatabase;
final DartObject annotation; final DartObject annotation;
@override
DriftElementKind get kind => isAccessor
? DriftElementKind.databaseAccessor
: DriftElementKind.database;
bool get isAccessor => !isDatabase; bool get isAccessor => !isDatabase;
DiscoveredBaseAccessor( DiscoveredBaseAccessor(

View File

@ -114,7 +114,12 @@ class DriftResolver {
_currentDependencyPath.add(reference); _currentDependencyPath.add(reference);
try { try {
final resolved = await resolveDiscovered(pending); final owningFile = driver.cache.stateForUri(reference.libraryUri);
await driver.discoverIfNecessary(owningFile);
final discovered = owningFile.discovery!.locallyDefinedElements
.firstWhere((e) => e.ownId == reference);
final resolved = await resolveDiscovered(discovered);
return ResolvedReferenceFound(resolved); return ResolvedReferenceFound(resolved);
} catch (e, s) { } catch (e, s) {
driver.backend.log.warning('Could not analze $reference', e, s); driver.backend.log.warning('Could not analze $reference', e, s);
@ -134,7 +139,8 @@ class DriftResolver {
Future<ResolveReferencedElementResult> resolveDartReference( Future<ResolveReferencedElementResult> resolveDartReference(
DriftElementId owner, Element element) async { DriftElementId owner, Element element) async {
final uri = await driver.backend.uriOfDart(element.library!); final uri = await driver.backend.uriOfDart(element.library!);
final state = await driver.prepareFileForAnalysis(uri); final state = driver.cache.stateForUri(uri);
await driver.discoverIfNecessary(driver.cache.stateForUri(uri));
final discovered = state.discovery?.locallyDefinedElements final discovered = state.discovery?.locallyDefinedElements
.whereType<DiscoveredDartElement>() .whereType<DiscoveredDartElement>()
@ -164,7 +170,7 @@ class DriftResolver {
for (final available in driver.cache.crawl(file)) { for (final available in driver.cache.crawl(file)) {
final localElementIds = { final localElementIds = {
...available.analysis.keys, ...available.analysis.keys,
...?available.discovery?.locallyDefinedElements.map((e) => e.ownId), ...available.definedElements.map((e) => e.$1),
}; };
for (final definedLocally in localElementIds) { for (final definedLocally in localElementIds) {

View File

@ -66,6 +66,9 @@ class DriftDatabase extends BaseDriftAccessor {
this.schemaVersion, this.schemaVersion,
this.accessors = const [], this.accessors = const [],
}); });
@override
DriftElementKind get kind => DriftElementKind.database;
} }
/// A Dart class with a similar API to a database, providing a view over a /// A Dart class with a similar API to a database, providing a view over a
@ -86,6 +89,9 @@ class DatabaseAccessor extends BaseDriftAccessor {
required this.databaseClass, required this.databaseClass,
required this.ownType, required this.ownType,
}); });
@override
DriftElementKind get kind => DriftElementKind.databaseAccessor;
} }
/// A query defined on a [BaseDriftAccessor]. /// A query defined on a [BaseDriftAccessor].

View File

@ -78,6 +78,8 @@ abstract class DriftElement {
final DriftElementId id; final DriftElementId id;
final DriftDeclaration declaration; final DriftDeclaration declaration;
DriftElementKind get kind;
/// All elements referenced by this element. /// All elements referenced by this element.
/// ///
/// References include the following: /// References include the following:
@ -104,6 +106,18 @@ abstract class DriftElement {
DriftElement(this.id, this.declaration); DriftElement(this.id, this.declaration);
} }
enum DriftElementKind {
table,
view,
dbIndex,
trigger,
database,
databaseAccessor,
definedQuery;
static Map<String, DriftElementKind> byName = values.asNameMap();
}
abstract class DriftSchemaElement extends DriftElement { abstract class DriftSchemaElement extends DriftElement {
DriftSchemaElement(super.id, super.declaration); DriftSchemaElement(super.id, super.declaration);

View File

@ -27,6 +27,9 @@ class DriftIndex extends DriftSchemaElement {
required this.createStmt, required this.createStmt,
}); });
@override
DriftElementKind get kind => DriftElementKind.dbIndex;
@override @override
String get dbGetterName => DriftSchemaElement.dbFieldName(id.name); String get dbGetterName => DriftSchemaElement.dbFieldName(id.name);

View File

@ -66,6 +66,9 @@ class DefinedSqlQuery extends DriftElement implements DriftQueryDeclaration {
} }
} }
@override
DriftElementKind get kind => DriftElementKind.definedQuery;
/// All in-line Dart source code literals embedded into the query. /// All in-line Dart source code literals embedded into the query.
final List<String> dartTokens; final List<String> dartTokens;

View File

@ -84,6 +84,9 @@ class DriftTable extends DriftElementWithResultSet {
late final DriftColumn? rowid = _findRowId(); late final DriftColumn? rowid = _findRowId();
@override
DriftElementKind get kind => DriftElementKind.table;
/// Whether this is a virtual table, created with a `CREATE VIRTUAL TABLE` /// Whether this is a virtual table, created with a `CREATE VIRTUAL TABLE`
/// statement in SQL. /// statement in SQL.
bool get isVirtual => virtualTableData != null; bool get isVirtual => virtualTableData != null;

View File

@ -31,6 +31,9 @@ class DriftTrigger extends DriftSchemaElement {
required this.writes, required this.writes,
}); });
@override
DriftElementKind get kind => DriftElementKind.trigger;
@override @override
String get dbGetterName => DriftSchemaElement.dbFieldName(id.name); String get dbGetterName => DriftSchemaElement.dbFieldName(id.name);

View File

@ -43,6 +43,9 @@ class DriftView extends DriftElementWithResultSet {
@override @override
String get dbGetterName => DriftSchemaElement.dbFieldName(id.name); String get dbGetterName => DriftSchemaElement.dbFieldName(id.name);
@override
DriftElementKind get kind => DriftElementKind.view;
/// Obtains all tables transitively referenced by the declaration of this /// Obtains all tables transitively referenced by the declaration of this
/// view. /// view.
/// ///

View File

@ -11,6 +11,52 @@ import '../../writer/writer.dart';
import 'backend.dart'; import 'backend.dart';
import 'exception.dart'; import 'exception.dart';
class DriftDiscover extends Builder {
final DriftOptions options;
DriftDiscover(BuilderOptions options)
: options = DriftOptions.fromJson(options.config);
@override
Map<String, List<String>> get buildExtensions => const {
'.drift': [
'.drift.drift_elements.json',
],
'.dart': [
'.dart.drift_elements.json',
],
};
@override
Future<void> build(BuildStep buildStep) async {
final backend = DriftBuildBackend(buildStep);
final driver = DriftAnalysisDriver(backend, options);
final prepared = await driver.findLocalElements(buildStep.inputId.uri);
final discovery = prepared.discovery;
if (discovery != null) {
await buildStep.writeAsString(
buildStep.allowedOutputs.single,
json.encode({
'valid_import': discovery.isValidImport,
'imports': [
for (final import in discovery.importDependencies)
import.toString(),
],
'elements': [
for (final entry in discovery.locallyDefinedElements)
{
'kind': entry.kind.name,
'name': entry.ownId.name,
}
]
}),
);
}
}
}
class DriftAnalyzer extends Builder { class DriftAnalyzer extends Builder {
final DriftOptions options; final DriftOptions options;
@ -32,7 +78,9 @@ class DriftAnalyzer extends Builder {
@override @override
Future<void> build(BuildStep buildStep) async { Future<void> build(BuildStep buildStep) async {
final backend = DriftBuildBackend(buildStep); final backend = DriftBuildBackend(buildStep);
final driver = DriftAnalysisDriver(backend, options); final driver = DriftAnalysisDriver(backend, options)
..cacheReader =
BuildCacheReader(buildStep, findsLocalElementsReliably: true);
final results = await driver.resolveElements(buildStep.inputId.uri); final results = await driver.resolveElements(buildStep.inputId.uri);
var hadWarnings = false; var hadWarnings = false;

View File

@ -2,6 +2,8 @@ import 'dart:convert';
import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element.dart';
import 'package:drift_dev/src/analysis/driver/state.dart';
import 'package:drift_dev/src/analysis/results/element.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:build/build.dart'; import 'package:build/build.dart';
import 'package:build/build.dart' as build; import 'package:build/build.dart' as build;
@ -100,10 +102,43 @@ class DriftBuildBackend extends DriftBackend {
class BuildCacheReader implements AnalysisResultCacheReader { class BuildCacheReader implements AnalysisResultCacheReader {
final BuildStep _buildStep; final BuildStep _buildStep;
BuildCacheReader(this._buildStep); @override
final bool findsLocalElementsReliably;
@override
final bool findsResolvedElementsReliably;
BuildCacheReader(
this._buildStep, {
this.findsLocalElementsReliably = false,
this.findsResolvedElementsReliably = false,
});
@override @override
Future<String?> readCacheFor(Uri uri) async { Future<CachedDiscoveryResults?> readDiscovery(Uri uri) async {
// For the format, see the `DriftDiscover` builder in `analyzer.dart`.
final assetId = AssetId.resolve(uri).addExtension('.drift_elements.json');
if (await _buildStep.canRead(assetId)) {
final results = json.decode(await _buildStep.readAsString(assetId));
final rawImports = results['imports'] as List;
final rawElements = (results['elements'] as List).cast<Map>();
return CachedDiscoveryResults(
results['valid_import'] as bool,
[for (final import in rawImports) Uri.parse(import as String)],
{
for (final element in rawElements)
(element['name'] as String):
DriftElementKind.byName[element['kind']]!,
},
);
}
return null;
}
@override
Future<String?> readElementCacheFor(Uri uri) async {
// These files are generated by the `DriftAnalyzer` builder // These files are generated by the `DriftAnalyzer` builder
final assetId = AssetId.resolve(uri).addExtension('.drift_module.json'); final assetId = AssetId.resolve(uri).addExtension('.drift_module.json');
if (await _buildStep.canRead(assetId)) { if (await _buildStep.canRead(assetId)) {

View File

@ -135,7 +135,14 @@ class _DriftBuildRun {
_DriftBuildRun(this.options, this.mode, this.buildStep) _DriftBuildRun(this.options, this.mode, this.buildStep)
: driver = DriftAnalysisDriver(DriftBuildBackend(buildStep), options) : driver = DriftAnalysisDriver(DriftBuildBackend(buildStep), options)
..cacheReader = BuildCacheReader(buildStep); ..cacheReader = BuildCacheReader(
buildStep,
// The discovery and analyzer builders will have emitted IR for
// every relevant file in a previous build step that this builder
// has a dependency on.
findsResolvedElementsReliably: !mode.embeddedAnalyzer,
findsLocalElementsReliably: !mode.embeddedAnalyzer,
);
Future<void> run() async { Future<void> run() async {
await _warnAboutDeprecatedOptions(); await _warnAboutDeprecatedOptions();
@ -214,12 +221,10 @@ class _DriftBuildRun {
/// Checks if the input file contains elements drift should generate code for. /// Checks if the input file contains elements drift should generate code for.
Future<bool> _checkForElementsToBuild() async { Future<bool> _checkForElementsToBuild() async {
if (mode.embeddedAnalyzer) { if (mode.embeddedAnalyzer) {
// Run the discovery step, which we'll have to run either way, to check if // Check if there are any elements defined locally that would need code
// there are any elements to generate code for. // to be generated for this file.
final state = await driver.prepareFileForAnalysis(buildStep.inputId.uri, final state = await driver.findLocalElements(buildStep.inputId.uri);
needsDiscovery: true); return state.definedElements.isNotEmpty;
return state.discovery?.locallyDefinedElements.isNotEmpty == true;
} else { } else {
// An analysis step should have already run for this asset. If we can't // An analysis step should have already run for this asset. If we can't
// pick up results from that, there is no code for drift to generate. // pick up results from that, there is no code for drift to generate.
@ -237,8 +242,8 @@ class _DriftBuildRun {
buildStep.inputId.extension != '.dart') { buildStep.inputId.extension != '.dart') {
// For modular drift file generation, we need to know about imports which // For modular drift file generation, we need to know about imports which
// are only available when discovery ran. // are only available when discovery ran.
await driver.prepareFileForAnalysis(buildStep.inputId.uri, final state = driver.cache.stateForUri(buildStep.inputId.uri);
needsDiscovery: true); await driver.discoverIfNecessary(state);
} }
return true; return true;

View File

@ -17,7 +17,7 @@ CREATE VIEW my_view AS SELECT whatever FROM unknown_table;
}); });
final uri = Uri.parse('package:a/main.drift'); final uri = Uri.parse('package:a/main.drift');
final state = await backend.driver.prepareFileForAnalysis(uri); final state = await backend.driver.findLocalElements(uri);
final discovered = state.discovery; final discovered = state.discovery;
DriftElementId id(String name) => DriftElementId(uri, name); DriftElementId id(String name) => DriftElementId(uri, name);
@ -51,8 +51,7 @@ CREATE TABLE valid_2 (bar INTEGER);
''', ''',
}); });
final state = await backend.driver final state = await backend.discoverLocalElements('package:a/main.drift');
.prepareFileForAnalysis(Uri.parse('package:a/main.drift'));
expect(state.errorsDuringDiscovery, [ expect(state.errorsDuringDiscovery, [
isDriftError(contains('Expected a table name')), isDriftError(contains('Expected a table name')),
]); ]);
@ -72,8 +71,7 @@ CREATE VIEW a AS VALUES(1,2,3);
''', ''',
}); });
final state = await backend.driver final state = await backend.discoverLocalElements('package:a/main.drift');
.prepareFileForAnalysis(Uri.parse('package:a/main.drift'));
expect(state.errorsDuringDiscovery, [ expect(state.errorsDuringDiscovery, [
isDriftError(contains('already defines an element named `a`')), isDriftError(contains('already defines an element named `a`')),
]); ]);
@ -89,8 +87,7 @@ CREATE VIEW a AS VALUES(1,2,3);
'a|lib/b.drift': "CREATE TABLE foo (bar INTEGER);", 'a|lib/b.drift': "CREATE TABLE foo (bar INTEGER);",
}); });
final state = await backend.driver final state = await backend.discoverLocalElements('package:a/a.drift');
.prepareFileForAnalysis(Uri.parse('package:a/a.drift'));
expect(state, hasNoErrors); expect(state, hasNoErrors);
expect( expect(
state.discovery, state.discovery,
@ -100,10 +97,11 @@ CREATE VIEW a AS VALUES(1,2,3);
[Uri.parse('package:a/b.drift')], [Uri.parse('package:a/b.drift')],
), ),
); );
expect( expect(
backend.driver.cache.knownFiles[Uri.parse('package:a/b.drift')], backend.driver.cache.knownFiles[Uri.parse('package:a/b.drift')],
isNotNull, isNull,
reason: 'Import should have been prepared as well', reason: 'Discovering local elements should not prepare other files',
); );
}); });
@ -113,23 +111,9 @@ CREATE VIEW a AS VALUES(1,2,3);
'a|lib/b.drift': "import 'a.drift';", 'a|lib/b.drift': "import 'a.drift';",
}); });
final state = await backend.driver final state = await backend.discoverLocalElements('package:a/a.drift');
.prepareFileForAnalysis(Uri.parse('package:a/a.drift'));
expect(state, hasNoErrors); expect(state, hasNoErrors);
}); });
test('emits warning on invalid import', () async {
final backend = TestBackend.inTest({
'a|lib/a.drift': "import 'b.drift';",
});
final state = await backend.driver
.prepareFileForAnalysis(Uri.parse('package:a/a.drift'));
expect(state.errorsDuringDiscovery, [
isDriftError(contains(
'The imported file, `package:a/b.drift`, does not exist'))
]);
});
}); });
}); });
@ -145,7 +129,7 @@ part 'a.dart';
}); });
final uri = Uri.parse('package:a/a.dart'); final uri = Uri.parse('package:a/a.dart');
final state = await backend.driver.prepareFileForAnalysis(uri); final state = await backend.driver.findLocalElements(uri);
expect(state, hasNoErrors); expect(state, hasNoErrors);
expect(state.discovery, isA<NotADartLibrary>()); expect(state.discovery, isA<NotADartLibrary>());
@ -169,7 +153,7 @@ class Groups extends Table {
}); });
final uri = Uri.parse('package:a/a.dart'); final uri = Uri.parse('package:a/a.dart');
final state = await backend.driver.prepareFileForAnalysis(uri); final state = await backend.driver.findLocalElements(uri);
expect(state, hasNoErrors); expect(state, hasNoErrors);
expect( expect(
@ -207,7 +191,7 @@ abstract class BaseRelationTable extends Table {
}); });
final uri = Uri.parse('package:a/a.dart'); final uri = Uri.parse('package:a/a.dart');
final state = await backend.driver.prepareFileForAnalysis(uri); final state = await backend.driver.findLocalElements(uri);
expect(state, hasNoErrors); expect(state, hasNoErrors);
expect( expect(
@ -244,8 +228,7 @@ class InvalidGetter extends Table {
}); });
for (final source in backend.sourceContents.keys) { for (final source in backend.sourceContents.keys) {
final state = final state = await backend.driver.findLocalElements(Uri.parse(source));
await backend.driver.prepareFileForAnalysis(Uri.parse(source));
expect( expect(
state.errorsDuringDiscovery, state.errorsDuringDiscovery,
@ -274,8 +257,8 @@ class B extends Table {
''', ''',
}); });
final state = await backend.driver final state =
.prepareFileForAnalysis(Uri.parse('package:a/a.dart')); await backend.driver.findLocalElements(Uri.parse('package:a/a.dart'));
expect(state.errorsDuringDiscovery, [ expect(state.errorsDuringDiscovery, [
isDriftError(contains('already defines an element named `tbl`')), isDriftError(contains('already defines an element named `tbl`')),

View File

@ -170,4 +170,16 @@ END;
}); });
}); });
}); });
test('emits warning on invalid import', () async {
final backend = TestBackend.inTest({
'a|lib/a.drift': "import 'b.drift';",
});
final state = await backend.analyze('package:a/a.drift');
expect(state.errorsDuringDiscovery, [
isDriftError(
contains('The imported file, `package:a/b.drift`, does not exist'))
]);
});
} }

View File

@ -208,6 +208,10 @@ class TestBackend extends DriftBackend {
Future<void> dispose() async {} Future<void> dispose() async {}
Future<FileState> discoverLocalElements(String uriString) {
return driver.findLocalElements(Uri.parse(uriString));
}
Future<FileState> analyze(String uriString) { Future<FileState> analyze(String uriString) {
return driver.fullyAnalyze(Uri.parse(uriString)); return driver.fullyAnalyze(Uri.parse(uriString));
} }

View File

@ -44,6 +44,7 @@ Future<RecordingAssetWriter> emulateDriftBuild({
]); ]);
final stages = [ final stages = [
discover(options),
preparingBuilder(options), preparingBuilder(options),
analyzer(options), analyzer(options),
modularBuild ? modular(options) : driftBuilderNotShared(options), modularBuild ? modular(options) : driftBuilderNotShared(options),