Skip to content

Commit ec6d434

Browse files
authored
Add new code for resolving deps graphs in a phased build. (#3953)
* Add new code for resolving deps graphs in a phased build. * Address review comments. * Address analyzer hint. * Address review comments.
1 parent e8122b2 commit ec6d434

20 files changed

+1882
-138
lines changed

build/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- Refactor `BuildCacheReader` to `BuildCacheAssetPathProvider`.
1515
- Refactor `FileBasedAssetReader` and `FileBasedAssetWriter` to `ReaderWriter`.
1616
- Move `BuildStepImpl` to `build_runner_core`, use `SingleStepReader` directly.
17+
- Add `LibraryCycleGraphLoader` for loading transitive deps for analysis.
1718

1819
## 2.4.2
1920

build/lib/src/internal.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
/// `build_runner_core` and `build_test` only.
77
library;
88

9+
export 'library_cycle_graph/asset_deps.dart';
10+
export 'library_cycle_graph/asset_deps_loader.dart';
11+
export 'library_cycle_graph/library_cycle.dart';
12+
export 'library_cycle_graph/library_cycle_graph.dart';
13+
export 'library_cycle_graph/library_cycle_graph_loader.dart';
14+
export 'library_cycle_graph/phased_reader.dart';
15+
export 'library_cycle_graph/phased_value.dart';
916
export 'state/asset_finder.dart';
1017
export 'state/asset_path_provider.dart';
1118
export 'state/filesystem.dart';
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 'package:built_collection/built_collection.dart';
6+
import 'package:built_value/built_value.dart';
7+
8+
import '../../build.dart' hide Builder;
9+
10+
part 'asset_deps.g.dart';
11+
12+
/// Dependencies of a Dart source asset.
13+
///
14+
/// A "dependency" is another Dart source mentioned in `import`, `export`,
15+
/// `part` or `part of`.
16+
///
17+
/// Missing or not-yet-generated sources can be represented by this class: they
18+
/// have no deps.
19+
abstract class AssetDeps implements Built<AssetDeps, AssetDepsBuilder> {
20+
static final AssetDeps empty = _$AssetDeps._(deps: BuiltSet());
21+
22+
BuiltSet<AssetId> get deps;
23+
24+
factory AssetDeps(Iterable<AssetId> deps) =>
25+
_$AssetDeps._(deps: BuiltSet.of(deps));
26+
AssetDeps._();
27+
}

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

Lines changed: 103 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 'package:analyzer/dart/analysis/utilities.dart';
6+
import 'package:analyzer/dart/ast/ast.dart';
7+
8+
import '../asset/id.dart';
9+
import 'asset_deps.dart';
10+
import 'phased_reader.dart';
11+
import 'phased_value.dart';
12+
13+
/// Loads Dart source assets to [PhasedValue]s of [AssetDeps].
14+
class AssetDepsLoader {
15+
static const _ignoredSchemes = ['dart', 'dart-ext'];
16+
17+
final PhasedReader _reader;
18+
19+
AssetDepsLoader(this._reader);
20+
21+
/// The phase that this loader is reading build state at.
22+
int get phase => _reader.phase;
23+
24+
/// Reads [id]
25+
///
26+
/// If [id] will be generated at a phase equal to or after [phase], the
27+
/// result is incomplete, with an expirey phase.
28+
Future<PhasedValue<AssetDeps>> load(AssetId id) async {
29+
final content = await _reader.readPhased(id);
30+
31+
return PhasedValue((b) {
32+
b.values.addAll(content.values.map((content) => _parse(id, content)));
33+
});
34+
}
35+
36+
/// Parses directives in [content] to return an [AssetDeps].
37+
ExpiringValue<AssetDeps> _parse(AssetId id, ExpiringValue<String> content) {
38+
final result =
39+
ExpiringValueBuilder<AssetDeps>()..expiresAfter = content.expiresAfter;
40+
41+
final parsed =
42+
parseString(content: content.value, throwIfDiagnostics: false).unit;
43+
44+
final depsNodeBuilder = AssetDepsBuilder();
45+
46+
for (final directive in parsed.directives) {
47+
if (directive is! UriBasedDirective) continue;
48+
final uri = directive.uri.stringValue;
49+
if (uri == null) continue;
50+
final parsedUri = Uri.parse(uri);
51+
if (_ignoredSchemes.any(parsedUri.isScheme)) continue;
52+
final assetId = AssetId.resolve(parsedUri, from: id);
53+
depsNodeBuilder.deps.add(assetId);
54+
}
55+
56+
result.value = depsNodeBuilder.build();
57+
return result.build();
58+
}
59+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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 'package:built_collection/built_collection.dart';
6+
import 'package:built_value/built_value.dart';
7+
8+
import '../asset/id.dart';
9+
10+
part 'library_cycle.g.dart';
11+
12+
/// A set of Dart source assets that mutually depend on each other.
13+
///
14+
/// This means they have to be compiled as a single unit.
15+
abstract class LibraryCycle
16+
implements Built<LibraryCycle, LibraryCycleBuilder> {
17+
BuiltSet<AssetId> get ids;
18+
19+
factory LibraryCycle([void Function(LibraryCycleBuilder) updates]) =
20+
_$LibraryCycle;
21+
LibraryCycle._();
22+
}

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

Lines changed: 104 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 'package:built_collection/built_collection.dart';
6+
import 'package:built_value/built_value.dart';
7+
8+
import '../asset/id.dart';
9+
import 'library_cycle.dart';
10+
11+
part 'library_cycle_graph.g.dart';
12+
13+
/// A directed acyclic graph of [LibraryCycle]s.
14+
abstract class LibraryCycleGraph
15+
implements Built<LibraryCycleGraph, LibraryCycleGraphBuilder> {
16+
LibraryCycle get root;
17+
BuiltList<LibraryCycleGraph> get children;
18+
19+
factory LibraryCycleGraph([void Function(LibraryCycleGraphBuilder) updates]) =
20+
_$LibraryCycleGraph;
21+
LibraryCycleGraph._();
22+
23+
/// All subgraphs in the graph, including the root.
24+
Iterable<LibraryCycleGraph> get transitiveGraphs {
25+
final result = Set<LibraryCycleGraph>.identity();
26+
final nextGraphs = [this];
27+
28+
while (nextGraphs.isNotEmpty) {
29+
final graph = nextGraphs.removeLast();
30+
if (result.add(graph)) {
31+
nextGraphs.addAll(graph.children);
32+
}
33+
}
34+
35+
return result;
36+
}
37+
38+
/// All assets in the graph, including the root.
39+
// TODO(davidmorgan): for best performance the graph should usually stay as a
40+
// graph rather than being expanded into an explicit set of nodes. So, remove
41+
// uses of this. If in the end it's still needed, investigate if it needs to
42+
// be optimized.
43+
Iterable<AssetId> get transitiveDeps sync* {
44+
for (final graph in transitiveGraphs) {
45+
yield* graph.root.ids;
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)