Skip to content

Commit 96a18f7

Browse files
committed
Track resolver dependencies as library cycle graphs.
1 parent b1e5b39 commit 96a18f7

36 files changed

+1408
-97
lines changed

_test_common/lib/test_phases.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ Future<void> wait(int milliseconds) =>
1919
Future.delayed(Duration(milliseconds: milliseconds));
2020

2121
void _printOnFailure(LogRecord record) {
22-
printOnFailure('$record');
22+
printOnFailure(
23+
'$record'
24+
'${record.error == null ? '' : ' ${record.error}'}'
25+
'${record.stackTrace == null ? '' : ' ${record.stackTrace}'}',
26+
);
2327
}
2428

2529
/// Runs [builders] in a test environment.

build/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- Refactor `FileBasedAssetReader` and `FileBasedAssetWriter` to `ReaderWriter`.
1616
- Move `BuildStepImpl` to `build_runner_core`, use `SingleStepReader` directly.
1717
- Add `LibraryCycleGraphLoader` for loading transitive deps for analysis.
18+
- Track resolver dependencies as library cycle graphs.
1819

1920
## 2.4.2
2021

build/lib/src/internal.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export 'library_cycle_graph/asset_deps_loader.dart';
1111
export 'library_cycle_graph/library_cycle.dart';
1212
export 'library_cycle_graph/library_cycle_graph.dart';
1313
export 'library_cycle_graph/library_cycle_graph_loader.dart';
14+
export 'library_cycle_graph/phased_asset_deps.dart';
1415
export 'library_cycle_graph/phased_reader.dart';
1516
export 'library_cycle_graph/phased_value.dart';
1617
export 'state/asset_finder.dart';

build/lib/src/library_cycle_graph/asset_deps.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:built_collection/built_collection.dart';
66
import 'package:built_value/built_value.dart';
7+
import 'package:built_value/serializer.dart';
78

89
import '../../build.dart' hide Builder;
910

@@ -17,11 +18,15 @@ part 'asset_deps.g.dart';
1718
/// Missing or not-yet-generated sources can be represented by this class: they
1819
/// have no deps.
1920
abstract class AssetDeps implements Built<AssetDeps, AssetDepsBuilder> {
21+
static Serializer<AssetDeps> get serializer => _$assetDepsSerializer;
22+
2023
static final AssetDeps empty = _$AssetDeps._(deps: BuiltSet());
2124

2225
BuiltSet<AssetId> get deps;
2326

2427
factory AssetDeps(Iterable<AssetId> deps) =>
2528
_$AssetDeps._(deps: BuiltSet.of(deps));
29+
factory AssetDeps.build(void Function(AssetDepsBuilder) updates) =
30+
_$AssetDeps;
2631
AssetDeps._();
2732
}

build/lib/src/library_cycle_graph/asset_deps.g.dart

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/lib/src/library_cycle_graph/asset_deps_loader.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:analyzer/dart/ast/ast.dart';
77

88
import '../asset/id.dart';
99
import 'asset_deps.dart';
10+
import 'phased_asset_deps.dart';
1011
import 'phased_reader.dart';
1112
import 'phased_value.dart';
1213

@@ -17,6 +18,8 @@ class AssetDepsLoader {
1718
final PhasedReader _reader;
1819

1920
AssetDepsLoader(this._reader);
21+
factory AssetDepsLoader.fromDeps(PhasedAssetDeps deps) =>
22+
_InMemoryAssetDepsLoader(deps);
2023

2124
/// The phase that this loader is reading build state at.
2225
int get phase => _reader.phase;
@@ -57,3 +60,32 @@ class AssetDepsLoader {
5760
return result.build();
5861
}
5962
}
63+
64+
// An [AssetDepsLoader] from already-loaded asset deps.
65+
class _InMemoryAssetDepsLoader implements AssetDepsLoader {
66+
final Future<PhasedValue<AssetDeps>> _empty = Future.value(
67+
PhasedValue.fixed(AssetDeps.empty),
68+
);
69+
PhasedAssetDeps phasedAssetDeps;
70+
71+
_InMemoryAssetDepsLoader(this.phasedAssetDeps);
72+
73+
// This is important: it prevents LibraryCycleGraphLoader from trying to load
74+
// data that is not in an incomplete [phasedAssetDeps].
75+
@override
76+
int get phase => phasedAssetDeps.phase;
77+
78+
@override
79+
ExpiringValue<AssetDeps> _parse(AssetId id, ExpiringValue<String> content) =>
80+
throw UnimplementedError();
81+
82+
@override
83+
PhasedReader get _reader => throw UnimplementedError();
84+
85+
@override
86+
Future<PhasedValue<AssetDeps>> load(AssetId id) {
87+
var result = phasedAssetDeps.assetDeps[id];
88+
if (result == null) return _empty;
89+
return Future.value(result);
90+
}
91+
}

build/lib/src/library_cycle_graph/library_cycle.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,7 @@ abstract class LibraryCycle
1919
factory LibraryCycle([void Function(LibraryCycleBuilder) updates]) =
2020
_$LibraryCycle;
2121
LibraryCycle._();
22+
23+
factory LibraryCycle.of(Iterable<AssetId> ids) =>
24+
_$LibraryCycle._(ids: ids.toBuiltSet());
2225
}

build/lib/src/library_cycle_graph/library_cycle_graph.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:built_collection/built_collection.dart';
66
import 'package:built_value/built_value.dart';
7+
import 'package:built_value/serializer.dart';
78

89
import '../asset/id.dart';
910
import 'library_cycle.dart';
@@ -13,6 +14,8 @@ part 'library_cycle_graph.g.dart';
1314
/// A directed acyclic graph of [LibraryCycle]s.
1415
abstract class LibraryCycleGraph
1516
implements Built<LibraryCycleGraph, LibraryCycleGraphBuilder> {
17+
static Serializer<LibraryCycleGraph> get serializer =>
18+
_$libraryCycleGraphSerializer;
1619
LibraryCycle get root;
1720
BuiltList<LibraryCycleGraph> get children;
1821

@@ -21,7 +24,7 @@ abstract class LibraryCycleGraph
2124
LibraryCycleGraph._();
2225

2326
/// All subgraphs in the graph, including the root.
24-
Iterable<LibraryCycleGraph> get transitiveGraphs {
27+
Iterable<LibraryCycleGraph> transitiveGraphs() {
2528
final result = Set<LibraryCycleGraph>.identity();
2629
final nextGraphs = [this];
2730

@@ -40,8 +43,8 @@ abstract class LibraryCycleGraph
4043
// graph rather than being expanded into an explicit set of nodes. So, remove
4144
// uses of this. If in the end it's still needed, investigate if it needs to
4245
// be optimized.
43-
Iterable<AssetId> get transitiveDeps sync* {
44-
for (final graph in transitiveGraphs) {
46+
Iterable<AssetId> transitiveDeps() sync* {
47+
for (final graph in transitiveGraphs()) {
4548
yield* graph.root.ids;
4649
}
4750
}

build/lib/src/library_cycle_graph/library_cycle_graph.g.dart

Lines changed: 75 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/lib/src/library_cycle_graph/library_cycle_graph_loader.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'asset_deps.dart';
1212
import 'asset_deps_loader.dart';
1313
import 'library_cycle.dart';
1414
import 'library_cycle_graph.dart';
15+
import 'phased_asset_deps.dart';
1516
import 'phased_value.dart';
1617

1718
/// Loads [LibraryCycleGraph]s during a phased build.
@@ -482,9 +483,14 @@ class LibraryCycleGraphLoader {
482483
AssetId id,
483484
) async {
484485
final graph = await libraryCycleGraphOf(assetDepsLoader, id);
485-
return graph.valueAt(phase: assetDepsLoader.phase).transitiveDeps;
486+
return graph.valueAt(phase: assetDepsLoader.phase).transitiveDeps();
486487
}
487488

489+
/// Serializable data from which the library cycle graphs can be
490+
/// reconstructed.
491+
PhasedAssetDeps phasedAssetDeps() =>
492+
PhasedAssetDeps((b) => b.assetDeps.addAll(_assetDeps));
493+
488494
@override
489495
String toString() => '''
490496
LibraryCycleGraphLoader(
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:math';
6+
7+
import 'package:built_collection/built_collection.dart';
8+
import 'package:built_value/built_value.dart';
9+
import 'package:built_value/serializer.dart';
10+
11+
import '../asset/id.dart';
12+
import 'asset_deps.dart';
13+
import 'phased_value.dart';
14+
15+
part 'phased_asset_deps.g.dart';
16+
17+
/// Serializable data from which library cycle graphs can be reconstructed.
18+
///
19+
/// Pass to `AssetDepsLoader.fromDeps` then use that to create a
20+
/// `LibraryCycleGraphLoader`.
21+
abstract class PhasedAssetDeps
22+
implements Built<PhasedAssetDeps, PhasedAssetDepsBuilder> {
23+
static Serializer<PhasedAssetDeps> get serializer =>
24+
_$phasedAssetDepsSerializer;
25+
26+
BuiltMap<AssetId, PhasedValue<AssetDeps>> get assetDeps;
27+
28+
factory PhasedAssetDeps([void Function(PhasedAssetDepsBuilder) b]) =
29+
_$PhasedAssetDeps;
30+
PhasedAssetDeps._();
31+
32+
factory PhasedAssetDeps.of(Map<AssetId, PhasedValue<AssetDeps>> assetDeps) =>
33+
_$PhasedAssetDeps._(assetDeps: assetDeps.build());
34+
35+
/// Returns this data with [other] added to it.
36+
PhasedAssetDeps addAll(PhasedAssetDeps other) {
37+
final result = toBuilder();
38+
for (final entry in other.assetDeps.entries) {
39+
result.assetDeps[entry.key] = entry.value;
40+
}
41+
return result.build();
42+
}
43+
44+
/// The max phase before there is any incomplete data, or 0xffffffff if there
45+
/// is no incomplete data.
46+
@memoized
47+
int get phase {
48+
int? result;
49+
for (final entry in assetDeps.values) {
50+
if (!entry.isComplete) {
51+
if (result == null) {
52+
result = entry.expiresAfter;
53+
} else {
54+
result = min(result, entry.expiresAfter!);
55+
}
56+
}
57+
}
58+
return result ?? 0xffffffff;
59+
}
60+
}

0 commit comments

Comments
 (0)