Skip to content
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

Add minimal AnalysisDriverModel implementation #3814

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build_resolvers/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
## 2.4.4-wip

- Refactor `BuildAssetUriResolver` into `AnalysisDriverModel` and
`AnalysisDriverModelUriResolver`.
`AnalysisDriverModelUriResolver`. Add new implementation of
`AnalysisDriverModel`.

## 2.4.3

Expand Down
132 changes: 125 additions & 7 deletions build_resolvers/lib/src/analysis_driver_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -19,36 +25,148 @@ 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).
davidmorgan marked this conversation as resolved.
Show resolved Hide resolved
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.s
davidmorgan marked this conversation as resolved.
Show resolved Hide resolved
Future<Set<AssetId>> _expandToTransitive(
AssetReader reader, Iterable<AssetId> ids) async {
final result = <AssetId>{};
await __expandToTransitive(reader, ids, result);
return result;
}

/// Walks the import graph from [ids], ignoring nodes already in [result].
///
/// Call with [result] empty to add full transitive deps to [result].
Future<void> __expandToTransitive(
davidmorgan marked this conversation as resolved.
Show resolved Hide resolved
AssetReader reader, Iterable<AssetId> ids, Set<AssetId> result) async {
final nextIds = Queue.of(ids);
while (nextIds.isNotEmpty) {
final nextId = nextIds.removeFirst();

// Skip if already seen.
if (!result.add(nextId)) continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid putting these in the queue in the first place instead? There could be a lot of duplicates otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


// Skip if not readable.
if (!await reader.canRead(nextId)) continue;
Copy link
Contributor

Choose a reason for hiding this comment

The 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 nextId just from checking for its existence

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


final content = await reader.readAsString(nextId);
final deps = _parseDependencies(content, nextId);
nextIds.addAll(deps.where((id) => !result.contains(id)));
}
}
}

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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class AnalysisDriverModelUriResolver implements UriResolver {
return null;
}

/// Path of [assetId] for the in-memory filesystem.
static String assetPath(AssetId assetId) =>
p.posix.join('/${assetId.package}', assetId.path);
}
19 changes: 9 additions & 10 deletions build_resolvers/lib/src/resolver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -412,16 +412,15 @@ class AnalyzerResolvers implements Resolvers {
AnalysisOptions? analysisOptions,
Future<String> Function()? sdkSummaryGenerator,
PackageConfig? packageConfig,
}) {
final buildAssetUriResolver = BuildAssetUriResolver();
return AnalyzerResolvers._(
analysisOptions: analysisOptions,
sdkSummaryGenerator: sdkSummaryGenerator,
packageConfig: packageConfig,
// Custom resolvers get their own asset uri resolver, as there should
// always be a 1:1 relationship between them.
analysisDriverModel: buildAssetUriResolver);
}
AnalysisDriverModel? analysisDriverModel,
}) =>
AnalyzerResolvers._(
analysisOptions: analysisOptions,
sdkSummaryGenerator: sdkSummaryGenerator,
packageConfig: packageConfig,
// Custom resolvers get their own asset uri resolver by default as
// there should always be a 1:1 relationship between them.
analysisDriverModel: analysisDriverModel ?? BuildAssetUriResolver());

/// See [AnalyzerResolvers.custom] for docs.
@Deprecated('Use either the AnalyzerResolvers.custom constructor or the '
Expand Down
Loading
Loading