-
Notifications
You must be signed in to change notification settings - Fork 214
Add minimal AnalysisDriverModel
implementation
#3814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
davidmorgan
merged 3 commits into
dart-lang:master
from
davidmorgan:test-analysis-driver-model
Feb 4, 2025
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,11 +3,17 @@ | |
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'dart:async'; | ||
import 'dart:collection'; | ||
|
||
import 'package:analyzer/dart/analysis/utilities.dart'; | ||
import 'package:analyzer/dart/ast/ast.dart'; | ||
import 'package:analyzer/file_system/memory_file_system.dart'; | ||
// ignore: implementation_imports | ||
import 'package:analyzer/src/clients/build_resolvers/build_resolvers.dart'; | ||
import 'package:build/build.dart'; | ||
import 'package:path/path.dart' as p; | ||
|
||
import 'analysis_driver_model_uri_resolver.dart'; | ||
|
||
/// Manages analysis driver and related build state. | ||
/// | ||
|
@@ -19,36 +25,145 @@ import 'package:build/build.dart'; | |
/// - Maintains an in-memory filesystem that is the analyzer's view of the | ||
/// build. | ||
/// - Notifies the analyzer of changes to that in-memory filesystem. | ||
abstract class AnalysisDriverModel { | ||
/// | ||
/// TODO(davidmorgan): the implementation here is unfinished and not used | ||
/// anywhere; finish it. See `build_asset_uri_resolver.dart` for the current | ||
/// implementation. | ||
class AnalysisDriverModel { | ||
/// In-memory filesystem for the analyzer. | ||
abstract final MemoryResourceProvider resourceProvider; | ||
final MemoryResourceProvider resourceProvider = | ||
MemoryResourceProvider(context: p.posix); | ||
|
||
/// Notifies that [step] has completed. | ||
/// | ||
/// All build steps must complete before [reset] is called. | ||
void notifyComplete(BuildStep step); | ||
void notifyComplete(BuildStep step) { | ||
// TODO(davidmorgan): add test coverage, fix implementation. | ||
} | ||
|
||
/// Clear cached information specific to an individual build. | ||
void reset(); | ||
void reset() { | ||
// TODO(davidmorgan): add test coverage, fix implementation. | ||
} | ||
|
||
/// Attempts to parse [uri] into an [AssetId] and returns it if it is cached. | ||
/// | ||
/// Handles 'package:' or 'asset:' URIs, as well as 'file:' URIs of the form | ||
/// `/$packageName/$assetPath`. | ||
/// | ||
/// Returns null if the Uri cannot be parsed or is not cached. | ||
AssetId? lookupCachedAsset(Uri uri); | ||
/// Returns null if the `Uri` cannot be parsed or is not cached. | ||
AssetId? lookupCachedAsset(Uri uri) { | ||
final assetId = AnalysisDriverModelUriResolver.parseAsset(uri); | ||
// TODO(davidmorgan): not clear if this is the right "exists" check. | ||
if (assetId == null || !resourceProvider.getFile(assetId.asPath).exists) { | ||
return null; | ||
} | ||
|
||
return assetId; | ||
} | ||
|
||
/// Updates [resourceProvider] and the analysis driver given by | ||
/// `withDriverResource` with updated versions of [entryPoints]. | ||
/// | ||
/// If [transitive], then all the transitive imports from [entryPoints] are | ||
/// also updated. | ||
/// | ||
/// Notifies [buildStep] of all inputs that result from analysis. If | ||
/// [transitive], this includes all transitive dependencies. | ||
/// | ||
/// If while finding transitive deps a `.transitive_deps` file is | ||
/// encountered next to a source file then this cuts off the reporting | ||
/// of deps to the [buildStep], but does not affect the reporting of | ||
/// files to the analysis driver. | ||
Future<void> performResolve( | ||
BuildStep buildStep, | ||
List<AssetId> entryPoints, | ||
Future<void> Function( | ||
FutureOr<void> Function(AnalysisDriverForPackageBuild)) | ||
withDriverResource, | ||
{required bool transitive}); | ||
{required bool transitive}) async { | ||
/// TODO(davidmorgan): add test coverage for whether transitive | ||
/// sources are read when [transitive] is false, fix the implementation | ||
/// here. | ||
/// TODO(davidmorgan): add test coverage for whether | ||
/// `.transitive_deps` files cut off the reporting of deps to the | ||
/// [buildStep], fix the implementation here. | ||
|
||
// Find transitive deps, this also informs [buildStep] of all inputs). | ||
final ids = await _expandToTransitive(buildStep, entryPoints); | ||
|
||
// Apply changes to in-memory filesystem. | ||
for (final id in ids) { | ||
if (await buildStep.canRead(id)) { | ||
final content = await buildStep.readAsString(id); | ||
|
||
/// TODO(davidmorgan): add test coverage for when a file is | ||
/// modified rather than added, fix the implementation here. | ||
resourceProvider.newFile(id.asPath, content); | ||
} else { | ||
if (resourceProvider.getFile(id.asPath).exists) { | ||
resourceProvider.deleteFile(id.asPath); | ||
} | ||
} | ||
} | ||
|
||
// Notify the analyzer of changes. | ||
await withDriverResource((driver) async { | ||
for (final id in ids) { | ||
// TODO(davidmorgan): add test coverage for over-notification of | ||
// changes, fix the implementaion here. | ||
driver.changeFile(id.asPath); | ||
} | ||
await driver.applyPendingFileChanges(); | ||
}); | ||
} | ||
|
||
/// Walks the import graph from [ids], returns full transitive deps. | ||
Future<Set<AssetId>> _expandToTransitive( | ||
AssetReader reader, Iterable<AssetId> ids) async { | ||
final result = ids.toSet(); | ||
final nextIds = Queue.of(ids); | ||
while (nextIds.isNotEmpty) { | ||
final nextId = nextIds.removeFirst(); | ||
|
||
// Skip if not readable. Note that calling `canRead` still makes it a | ||
// dependency of the `BuildStep`. | ||
if (!await reader.canRead(nextId)) continue; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be worth leaving a comment that this does add the dependency on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
final content = await reader.readAsString(nextId); | ||
final deps = _parseDependencies(content, nextId); | ||
|
||
// For each dep, if it's not in `result` yet, it's newly-discovered: | ||
// add it to `nextIds`. | ||
for (final dep in deps) { | ||
if (result.add(dep)) { | ||
nextIds.add(dep); | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
} | ||
|
||
const _ignoredSchemes = ['dart', 'dart-ext']; | ||
|
||
/// Parses Dart source in [content], returns all depedencies: all assets | ||
/// mentioned in directives, excluding `dart:` and `dart-ext` schemes. | ||
List<AssetId> _parseDependencies(String content, AssetId from) => | ||
parseString(content: content, throwIfDiagnostics: false) | ||
davidmorgan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.unit | ||
.directives | ||
.whereType<UriBasedDirective>() | ||
.map((directive) => directive.uri.stringValue) | ||
// Uri.stringValue can be null for strings that use interpolation. | ||
.nonNulls | ||
.where( | ||
(uriContent) => !_ignoredSchemes.any(Uri.parse(uriContent).isScheme), | ||
) | ||
davidmorgan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.map((content) => AssetId.resolve(Uri.parse(content), from: from)) | ||
.toList(); | ||
|
||
extension _AssetIdExtensions on AssetId { | ||
/// Asset path for the in-memory filesystem. | ||
String get asPath => AnalysisDriverModelUriResolver.assetPath(this); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.