Skip to content

Commit

Permalink
Refactor: split BuildAssetUriResolver into AnalysisDriverModel an…
Browse files Browse the repository at this point in the history
…dd `AnalysisDriverModelUriResolver`. (#3813)

* Refactor: split `BuildAssetUriResolver` into `AnalysisDriverModel` and `AnalysisDriverModelUriResolver`.

`AnalysisDriverModel` is `build_runner`'s state related to build steps that use analysis, including an in-memory filesystem for use by the analyzer.

`UriResolver` is the analyzer's view of that state.

* Address review comments.
  • Loading branch information
davidmorgan authored Feb 3, 2025
1 parent 4c76b74 commit 72b4f5b
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 67 deletions.
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);
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)),
);
}
}

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

0 comments on commit 72b4f5b

Please sign in to comment.