Skip to content

Refactor: split BuildAssetUriResolver into AnalysisDriverModel andd AnalysisDriverModelUriResolver. #3813

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
Merged
Show file tree
Hide file tree
Changes from all commits
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: 3 additions & 0 deletions build_resolvers/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 2.4.4-wip

- Refactor `BuildAssetUriResolver` into `AnalysisDriverModel` and
`AnalysisDriverModelUriResolver`.

## 2.4.3

- Require the latest analyzer, and stop passing the `withNullability`
Expand Down
12 changes: 7 additions & 5 deletions build_resolvers/lib/src/analysis_driver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import 'package:package_config/package_config.dart' show PackageConfig;
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';

import 'analysis_driver_model.dart';
import 'analysis_driver_model_uri_resolver.dart';
import 'build_asset_uri_resolver.dart';

/// Builds an [AnalysisDriverForPackageBuild] backed by a summary SDK.
///
/// Any code must be resolvable through [buildAssetUriResolver].
/// Any code must be resolvable through [analysisDriverModel].
Future<AnalysisDriverForPackageBuild> analysisDriver(
BuildAssetUriResolver buildAssetUriResolver,
AnalysisDriverModel analysisDriverModel,
AnalysisOptions analysisOptions,
String sdkSummaryPath,
PackageConfig packageConfig,
Expand All @@ -27,12 +29,12 @@ Future<AnalysisDriverForPackageBuild> analysisDriver(
analysisOptions: analysisOptions,
packages: _buildAnalyzerPackages(
packageConfig,
buildAssetUriResolver.resourceProvider,
analysisDriverModel.resourceProvider,
),
resourceProvider: buildAssetUriResolver.resourceProvider,
resourceProvider: analysisDriverModel.resourceProvider,
sdkSummaryBytes: File(sdkSummaryPath).readAsBytesSync(),
uriResolvers: [
buildAssetUriResolver,
AnalysisDriverModelUriResolver(analysisDriverModel),
],
);
}
Expand Down
54 changes: 54 additions & 0 deletions build_resolvers/lib/src/analysis_driver_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

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';

/// Manages analysis driver and related build state.
///
/// - Tracks the import graph of all sources needed for analysis.
/// - Given a set of entrypoints, adds them to known sources, optionally with
/// their transitive imports.
/// - Given a set of entrypoints, informs a `BuildStep` which inputs it now
/// depends on because of analysis.
/// - 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 {
/// In-memory filesystem for the analyzer.
abstract final MemoryResourceProvider resourceProvider;

/// Notifies that [step] has completed.
///
/// All build steps must complete before [reset] is called.
void notifyComplete(BuildStep step);

/// Clear cached information specific to an individual build.
void reset();

/// 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);

/// 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.
Future<void> performResolve(
BuildStep buildStep,
List<AssetId> entryPoints,
Future<void> Function(
FutureOr<void> Function(AnalysisDriverForPackageBuild))
withDriverResource,
{required bool transitive});
}
72 changes: 72 additions & 0 deletions build_resolvers/lib/src/analysis_driver_model_uri_resolver.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:analyzer/source/file_source.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.dart';

const _ignoredSchemes = ['dart', 'dart-ext'];

/// A [UriResolver] on top of [AnalysisDriverModel]'s in-memory filesystem
///
/// This is the analyzer's view of the current build: the files that
/// `build_runner` wants the analyzer to analyze.
///
/// The in-memory filesystem uses POSIX-style paths with `package:foo/bar'
/// mapping to `/foo/bar`.
class AnalysisDriverModelUriResolver implements UriResolver {
final AnalysisDriverModel analysisDriverModel;
AnalysisDriverModelUriResolver(this.analysisDriverModel);

@override
Source? resolveAbsolute(Uri uri, [Uri? actualUri]) {
final assetId = parseAsset(uri);
if (assetId == null) return null;

var file = analysisDriverModel.resourceProvider.getFile(assetPath(assetId));
return FileSource(file, assetId.uri);
}

@override
Uri pathToUri(String path) {
var pathSegments = p.posix.split(path);
Copy link

Choose a reason for hiding this comment

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

I see that this is just copied from the other file but just reading this I have no idea why it should be posix? And why is it strings at all?
I'm not saying change it now (you probably shouldn't with this being splitting stuff out) - just noting it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

UriResolver is an interface owned by the analyzer that abstracts over a filesystem, it gives it a way to turn URIs into source+paths and paths into URIs.

This class is the view build_runner gives the analyzer of the files it wants to be analyzer.

The paths can be anything as long as they map 1:1 to URIs, so the exact path format is an implementation detail here.

Expanded the class comment to mention some of this:

/// A [UriResolver] on top of [AnalysisDriverModel]'s in-memory filesystem
///
/// This is the analyzer's view of the current build: the files that
/// build_runner wants the analyzer to analyze.
///
/// The in-memory filesystem uses POSIX-style paths with package:foo/bar' /// mapping to /foo/bar`.

var packageName = pathSegments[1];
if (pathSegments[2] == 'lib') {
return Uri(
scheme: 'package',
pathSegments: [packageName].followedBy(pathSegments.skip(3)),
Copy link

Choose a reason for hiding this comment

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

Again I realize this is just copied from other places, so you don't have to change it here.
I'll just say that we, at least in my opinion, are not doing ourselves any favors by using followedBy and skip. We're creating a list and two iterables.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks.

The analyzer only calls this O(N) times, where N is the number of files; so that won't matter until we've fixed the O(N^2) behaviour. I look forward to getting to that point ;)

);
} else {
return Uri(
scheme: 'asset',
pathSegments: [packageName].followedBy(pathSegments.skip(2)),
);
}
}

/// Attempts to parse [uri] into an [AssetId].
///
/// Handles 'package:' or 'asset:' URIs, as well as 'file:' URIs that have the
/// same pattern used by [assetPath].
///
/// Returns null if the Uri cannot be parsed.
static AssetId? parseAsset(Uri uri) {
if (_ignoredSchemes.any(uri.isScheme)) return null;
if (uri.isScheme('package') || uri.isScheme('asset')) {
return AssetId.resolve(uri);
}
if (uri.isScheme('file')) {
final parts = p.split(uri.path);
return AssetId(parts[1], p.posix.joinAll(parts.skip(2)));
}
return null;
}

static String assetPath(AssetId assetId) =>
p.posix.join('/${assetId.package}', assetId.path);
}
48 changes: 8 additions & 40 deletions build_resolvers/lib/src/build_asset_uri_resolver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import 'dart:isolate';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/source/file_source.dart';
// ignore: implementation_imports
import 'package:analyzer/src/clients/build_resolvers/build_resolvers.dart';
import 'package:build/build.dart' show AssetId, BuildStep;
Expand All @@ -19,11 +18,13 @@ import 'package:graphs/graphs.dart';
import 'package:path/path.dart' as p;
import 'package:stream_transform/stream_transform.dart';

import 'analysis_driver_model.dart';

const _ignoredSchemes = ['dart', 'dart-ext'];

const transitiveDigestExtension = '.transitive_digest';

class BuildAssetUriResolver extends UriResolver {
class BuildAssetUriResolver implements AnalysisDriverModel {
/// A cache of the directives for each Dart library.
///
/// This is stored across builds and is only invalidated if we read a file and
Expand All @@ -41,6 +42,7 @@ class BuildAssetUriResolver extends UriResolver {
/// updated in the analysis driver.
final _needsChangeFile = HashSet<String>();

@override
final resourceProvider = MemoryResourceProvider(context: p.posix);

/// The assets which are known to be readable at some point during the current
Expand All @@ -66,11 +68,7 @@ class BuildAssetUriResolver extends UriResolver {
/// This is not used within testing contexts or similar custom contexts.
static final BuildAssetUriResolver sharedInstance = BuildAssetUriResolver();

/// 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.
@override
Future<void> performResolve(
BuildStep buildStep,
List<AssetId> entryPoints,
Expand Down Expand Up @@ -186,12 +184,7 @@ class BuildAssetUriResolver extends UriResolver {
return null;
}

/// Attempts to parse [uri] into an [AssetId] and returns it if it is cached.
///
/// Handles 'package:' or 'asset:' URIs, as well as 'file:' URIs that have the
/// same pattern used by [assetPath].
///
/// Returns null if the Uri cannot be parsed or is not cached.
@override
AssetId? lookupCachedAsset(Uri uri) {
final assetId = parseAsset(uri);
if (assetId == null || !_cachedAssetDigests.containsKey(assetId)) {
Expand All @@ -201,43 +194,18 @@ class BuildAssetUriResolver extends UriResolver {
return assetId;
}

@override
void notifyComplete(BuildStep step) {
_buildStepTransitivelyResolvedAssets.remove(step);
}

/// Clear cached information specific to an individual build.
@override
void reset() {
assert(_buildStepTransitivelyResolvedAssets.isEmpty,
'Reset was called before all build steps completed');
globallySeenAssets.clear();
_needsChangeFile.clear();
}

@override
Source? resolveAbsolute(Uri uri, [Uri? actualUri]) {
final assetId = parseAsset(uri);
if (assetId == null) return null;

var file = resourceProvider.getFile(assetPath(assetId));
return FileSource(file, assetId.uri);
}

@override
Uri pathToUri(String path) {
var pathSegments = p.posix.split(path);
var packageName = pathSegments[1];
if (pathSegments[2] == 'lib') {
return Uri(
scheme: 'package',
pathSegments: [packageName].followedBy(pathSegments.skip(3)),
);
} else {
return Uri(
scheme: 'asset',
pathSegments: [packageName].followedBy(pathSegments.skip(2)),
);
}
}
}

String assetPath(AssetId assetId) =>
Expand Down
Loading
Loading