Skip to content
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
4 changes: 2 additions & 2 deletions build_runner/lib/src/build/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import 'library_cycle_graph/library_cycle_graph_loader.dart';
import 'performance_tracker.dart';
import 'performance_tracking_resolvers.dart';
import 'resolver/analysis_driver_model.dart';
import 'resolver/resolver.dart';
import 'resolver/resolvers_impl.dart';
import 'run_builder.dart';
import 'run_post_process_builder.dart';
import 'single_step_reader_writer.dart';
Expand Down Expand Up @@ -203,7 +203,7 @@ class Build {
}

Resolvers get _resolvers =>
testingOverrides.resolvers ?? AnalyzerResolvers.sharedInstance;
testingOverrides.resolvers ?? ResolversImpl.sharedInstance;

Future<void> _updateAssetGraph(Map<AssetId, ChangeType> updates) async {
changedInputs.clear();
Expand Down
62 changes: 20 additions & 42 deletions build_runner/lib/src/build/resolver/analysis_driver_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import 'package:analyzer/src/clients/build_resolvers/build_resolvers.dart';
import 'package:build/build.dart';

import '../../logging/timed_activities.dart';
import '../build_step_impl.dart';
import '../input_tracker.dart';
import '../library_cycle_graph/asset_deps_loader.dart';
import '../library_cycle_graph/library_cycle_graph_loader.dart';
import '../library_cycle_graph/phased_asset_deps.dart';
import '../library_cycle_graph/phased_reader.dart';
import 'analysis_driver_filesystem.dart';

/// Manages analysis driver and related build state.
Expand Down Expand Up @@ -68,41 +69,21 @@ class AnalysisDriverModel {
return assetId;
}

/// Updates [filesystem] and the analysis driver given by
/// `withDriverResource` with updated versions of [entrypoints].
/// Updates [filesystem] and the driver provided by [withDriver] with updated
/// source of [entrypoint] at the phase viewed by [phasedReader].
///
/// If [transitive], then all the transitive imports from [entrypoints] are
/// If [transitive], then all the transitive imports from [entrypoint] are
/// also updated.
///
/// Notifies [buildStep] of all inputs that result from analysis. If
/// [transitive], this includes all transitive dependencies.
///
Future<void> performResolve(
BuildStep buildStep,
List<AssetId> entrypoints,
Future<void> Function(
FutureOr<void> Function(AnalysisDriverForPackageBuild),
)
withDriverResource, {
required bool transitive,
}) async {
for (final entrypoint in entrypoints) {
await _performResolve(
buildStep as BuildStepImpl,
entrypoint,
withDriverResource,
transitive: transitive,
);
}
}

Future<void> _performResolve(
BuildStepImpl buildStep,
AssetId entrypoint,
Future<void> Function(
FutureOr<void> Function(AnalysisDriverForPackageBuild),
/// Records what was read in [inputTracker].
Future<void> updateDriver({
required Future<void> Function(
Future<void> Function(AnalysisDriverForPackageBuild),
)
withDriverResource, {
withDriver,
required AssetId entrypoint,
required PhasedReader phasedReader,
required InputTracker inputTracker,
required bool transitive,
}) async {
Iterable<AssetId> idsToSyncOntoFilesystem;
Expand All @@ -113,19 +94,19 @@ class AnalysisDriverModel {
// Note: `transitiveDepsOf` can cause loads that cause builds that
// cause a recursive `_performResolve` on this same `AnalysisDriver`
// instance.
final nodeLoader = AssetDepsLoader(buildStep.phasedReader);
buildStep.inputTracker.addResolverEntrypoint(entrypoint);
final nodeLoader = AssetDepsLoader(phasedReader);
inputTracker.addResolverEntrypoint(entrypoint);
return await _graphLoader.transitiveDepsOf(nodeLoader, entrypoint);
});
} else {
// Notify [buildStep] of its inputs.
buildStep.inputTracker.add(entrypoint);
inputTracker.add(entrypoint);
idsToSyncOntoFilesystem = [entrypoint];
}

await withDriverResource((driver) async {
await withDriver((driver) async {
// Sync changes onto the "URI resolver", the in-memory filesystem.
final phase = buildStep.phasedReader.phase;
final phase = phasedReader.phase;
for (final id in idsToSyncOntoFilesystem) {
final wasSyncedAt = _syncedOntoFilesystemAtPhase[id];
if (wasSyncedAt != null) {
Expand All @@ -134,10 +115,7 @@ class AnalysisDriverModel {
continue;
}
// Skip if synced at an equivalent other phase.
if (!buildStep.phasedReader.hasChanged(
id,
comparedToPhase: wasSyncedAt,
)) {
if (!phasedReader.hasChanged(id, comparedToPhase: wasSyncedAt)) {
continue;
}
}
Expand All @@ -147,7 +125,7 @@ class AnalysisDriverModel {
// Tracking has already been done by calling `inputTracker` directly.
// Use `phasedReader` for the read instead of the `buildStep` methods
// `canRead` and `readAsString`, which would call `inputTracker`.
final content = await buildStep.phasedReader.readAtPhase(id);
final content = await phasedReader.readAtPhase(id);
if (content == null) {
filesystem.deleteFile(id.asPath);
} else {
Expand Down
242 changes: 242 additions & 0 deletions build_runner/lib/src/build/resolver/build_resolver.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
// Copyright (c) 2026, 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/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
// ignore: implementation_imports
import 'package:analyzer/src/clients/build_resolvers/build_resolvers.dart';
import 'package:build/build.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:pool/pool.dart';

import '../../logging/timed_activities.dart';
import '../input_tracker.dart';
import '../library_cycle_graph/phased_reader.dart';
import 'analysis_driver_filesystem.dart';
import 'analysis_driver_model.dart';
import 'build_step_resolver.dart';

/// [Resolver] for the entire build.
///
/// Omits methods only applicable to a single build step, so it doesn't
/// implement the [Resolver] interface.
///
/// Use via a [BuildStepResolver].
class BuildResolver {
final AnalysisDriverModel _analysisDriverModel;
final AnalysisDriverForPackageBuild _driver;
final AnalyzeActivityPool _driverPool;

Future<List<LibraryElement>>? _sdkLibraries;

BuildResolver(this._driver, Pool driverPool, this._analysisDriverModel)
: _driverPool = AnalyzeActivityPool(driverPool);

Future<bool> isLibrary(AssetId assetId) async {
if (assetId.extension != '.dart') return false;
return _driverPool.withResource(() async {
if (!_driver.isUriOfExistingFile(assetId.uri)) return false;
final result =
_driver.currentSession.getFile(
AnalysisDriverFilesystem.assetPath(assetId),
)
as FileResult;
return !result.isPart;
});
}

Future<AstNode?> astNodeFor(Fragment fragment, {bool resolve = false}) async {
final library = fragment.libraryFragment?.element;
if (library == null) {
// Invalid elements (e.g. an MultiplyDefinedElement) are not part of any
// library and can't be resolved like this.
return null;
}
final path = library.firstFragment.source.fullName;

return _driverPool.withResource(() async {
final session = _driver.currentSession;
if (resolve) {
final result =
await session.getResolvedLibrary(path) as ResolvedLibraryResult;
if (fragment is LibraryFragment) {
return result.unitWithPath(fragment.source.fullName)?.unit;
}
return result.getFragmentDeclaration(fragment)?.node;
} else {
final result = session.getParsedLibrary(path) as ParsedLibraryResult;
if (fragment is LibraryFragment) {
final unitPath = fragment.source.fullName;
return result.units
.firstWhereOrNull((unit) => unit.path == unitPath)
?.unit;
}
return result.getFragmentDeclaration(fragment)?.node;
}
});
}

Future<CompilationUnit> compilationUnitFor(
AssetId assetId, {
bool allowSyntaxErrors = false,
}) {
return _driverPool.withResource(() async {
if (!_driver.isUriOfExistingFile(assetId.uri)) {
throw AssetNotFoundException(assetId);
}

final path = AnalysisDriverFilesystem.assetPath(assetId);

final parsedResult =
_driver.currentSession.getParsedUnit(path) as ParsedUnitResult;
if (!allowSyntaxErrors &&
parsedResult.diagnostics.any((e) => e.severity == Severity.error)) {
throw SyntaxErrorInAssetException(assetId, [parsedResult]);
}
return parsedResult.unit;
});
}

Future<LibraryElement> libraryFor(
AssetId assetId, {
bool allowSyntaxErrors = false,
}) async {
final library = await _driverPool.withResource(() async {
final uri = assetId.uri;
if (!_driver.isUriOfExistingFile(uri)) {
throw AssetNotFoundException(assetId);
}

final path = AnalysisDriverFilesystem.assetPath(assetId);
final parsedResult = _driver.currentSession.getParsedUnit(path);
if (parsedResult is! ParsedUnitResult || parsedResult.isPart) {
throw NonLibraryAssetException(assetId);
}

return await _driver.currentSession.getLibraryByUri(uri.toString())
as LibraryElementResult;
});

if (!allowSyntaxErrors) {
final errors = await _syntacticErrorsFor(library.element);
if (errors.isNotEmpty) {
throw SyntaxErrorInAssetException(assetId, errors);
}
}

return library.element;
}

/// Finds syntax errors in files related to the [element].
///
/// This includes the main library and existing part files.
Future<List<ErrorsResult>> _syntacticErrorsFor(LibraryElement element) async {
final existingSources = <Source>[];

for (final fragment in element.fragments) {
existingSources.add(fragment.source);
}

// Map from elements to absolute paths
final paths = existingSources
.map((source) => _analysisDriverModel.lookupCachedAsset(source.uri))
.whereType<AssetId>() // filter out nulls
.map(AnalysisDriverFilesystem.assetPath);

final relevantResults = <ErrorsResult>[];

await _driverPool.withResource(() async {
for (final path in paths) {
final result = await _driver.currentSession.getErrors(path);
if (result is ErrorsResult &&
result.diagnostics.any(
(error) =>
error.diagnosticCode.type == DiagnosticType.SYNTACTIC_ERROR,
)) {
relevantResults.add(result);
}
}
});

return relevantResults;
}

Stream<LibraryElement> get sdkLibraries {
final loadLibraries =
_sdkLibraries ??= Future.sync(() {
final publicSdkUris = _driver.sdkLibraryUris.where(
(e) => !e.path.startsWith('_'),
);

return Future.wait(
publicSdkUris.map((uri) {
return _driverPool.withResource(() async {
final result =
await _driver.currentSession.getLibraryByUri(uri.toString())
as LibraryElementResult;
return result.element;
});
}),
);
});

return Stream.fromFuture(loadLibraries).expand((libraries) => libraries);
}

Future<AssetId> assetIdForElement(Element element) async {
if (element is MultiplyDefinedElement) {
throw UnresolvableAssetException('${element.name} is ambiguous');
}

final source = element.firstFragment.libraryFragment?.source;
if (source == null) {
throw UnresolvableAssetException(
'${element.name} does not have a source',
);
}

final uri = source.uri;
if (!uri.isScheme('package') && !uri.isScheme('asset')) {
throw UnresolvableAssetException('${element.name} in ${source.uri}');
}
return AssetId.resolve(source.uri);
}

/// Updates the analysis driver with updated source of [entrypoint] at
/// the phase viewed by [phasedReader].
///
/// If [transitive], then all the transitive imports from [entrypoint] are
/// also updated.
///
/// Records what was read in [inputTracker].
Future<void> updateDriverForEntrypoint({
required AssetId entrypoint,
required PhasedReader phasedReader,
required InputTracker inputTracker,
required bool transitive,
}) => _analysisDriverModel.updateDriver(
withDriver:
(withDriver) => _driverPool.withResource(() => withDriver(_driver)),
phasedReader: phasedReader,
inputTracker: inputTracker,
entrypoint: entrypoint,
transitive: transitive,
);
}

/// Wraps [pool] so resource use is timed as [TimedActivity.analyze].
class AnalyzeActivityPool {
final Pool pool;

AnalyzeActivityPool(this.pool);

Future<T> withResource<T>(Future<T> Function() function) async {
return pool.withResource(() => TimedActivity.analyze.runAsync(function));
}
}
Loading
Loading