Skip to content

Add new code for resolving deps graphs in a phased build. #3953

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
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
1 change: 1 addition & 0 deletions build/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Refactor `BuildCacheReader` to `BuildCacheAssetPathProvider`.
- Refactor `FileBasedAssetReader` and `FileBasedAssetWriter` to `ReaderWriter`.
- Move `BuildStepImpl` to `build_runner_core`, use `SingleStepReader` directly.
- Add `LibraryCycleGraphLoader` for loading transitive deps for analysis.

## 2.4.2

Expand Down
7 changes: 7 additions & 0 deletions build/lib/src/internal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
/// `build_runner_core` and `build_test` only.
library;

export 'library_cycle_graph/asset_deps.dart';
export 'library_cycle_graph/asset_deps_loader.dart';
export 'library_cycle_graph/library_cycle.dart';
export 'library_cycle_graph/library_cycle_graph.dart';
export 'library_cycle_graph/library_cycle_graph_loader.dart';
export 'library_cycle_graph/phased_reader.dart';
export 'library_cycle_graph/phased_value.dart';
export 'state/asset_finder.dart';
export 'state/asset_path_provider.dart';
export 'state/filesystem.dart';
Expand Down
27 changes: 27 additions & 0 deletions build/lib/src/library_cycle_graph/asset_deps.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';

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

part 'asset_deps.g.dart';

/// Dependencies of a Dart source asset.
///
/// A "dependency" is another Dart source mentioned in `import`, `export`,
/// `part` or `part of`.
///
/// Missing or not-yet-generated sources can be represented by this class: they
/// have no deps.
abstract class AssetDeps implements Built<AssetDeps, AssetDepsBuilder> {
static final AssetDeps empty = _$AssetDeps._(deps: BuiltSet());

BuiltSet<AssetId> get deps;

factory AssetDeps(Iterable<AssetId> deps) =>
_$AssetDeps._(deps: BuiltSet.of(deps));
AssetDeps._();
}
103 changes: 103 additions & 0 deletions build/lib/src/library_cycle_graph/asset_deps.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions build/lib/src/library_cycle_graph/asset_deps_loader.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';

import '../asset/id.dart';
import 'asset_deps.dart';
import 'phased_reader.dart';
import 'phased_value.dart';

/// Loads Dart source assets to [PhasedValue]s of [AssetDeps].
class AssetDepsLoader {
static const _ignoredSchemes = ['dart', 'dart-ext'];

final PhasedReader _reader;

AssetDepsLoader(this._reader);

/// The phase that this loader is reading build state at.
int get phase => _reader.phase;

/// Reads [id]
///
/// If [id] will be generated at a phase equal to or after [phase], the
/// result is incomplete, with an expirey phase.
Future<PhasedValue<AssetDeps>> load(AssetId id) async {
final content = await _reader.readPhased(id);

return PhasedValue((b) {
b.values.addAll(content.values.map((content) => _parse(id, content)));
});
}

/// Parses directives in [content] to return an [AssetDeps].
ExpiringValue<AssetDeps> _parse(AssetId id, ExpiringValue<String> content) {
final result =
ExpiringValueBuilder<AssetDeps>()..expiresAfter = content.expiresAfter;

final parsed =
parseString(content: content.value, throwIfDiagnostics: false).unit;

final depsNodeBuilder = AssetDepsBuilder();

for (final directive in parsed.directives) {
if (directive is! UriBasedDirective) continue;
final uri = directive.uri.stringValue;
if (uri == null) continue;
final parsedUri = Uri.parse(uri);
if (_ignoredSchemes.any(parsedUri.isScheme)) continue;
final assetId = AssetId.resolve(parsedUri, from: id);
depsNodeBuilder.deps.add(assetId);
}

result.value = depsNodeBuilder.build();
return result.build();
}
}
22 changes: 22 additions & 0 deletions build/lib/src/library_cycle_graph/library_cycle.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';

import '../asset/id.dart';

part 'library_cycle.g.dart';

/// A set of Dart source assets that mutually depend on each other.
///
/// This means they have to be compiled as a single unit.
abstract class LibraryCycle
implements Built<LibraryCycle, LibraryCycleBuilder> {
BuiltSet<AssetId> get ids;

factory LibraryCycle([void Function(LibraryCycleBuilder) updates]) =
_$LibraryCycle;
LibraryCycle._();
}
104 changes: 104 additions & 0 deletions build/lib/src/library_cycle_graph/library_cycle.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions build/lib/src/library_cycle_graph/library_cycle_graph.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';

import '../asset/id.dart';
import 'library_cycle.dart';

part 'library_cycle_graph.g.dart';

/// A directed acyclic graph of [LibraryCycle]s.
abstract class LibraryCycleGraph
implements Built<LibraryCycleGraph, LibraryCycleGraphBuilder> {
LibraryCycle get root;
BuiltList<LibraryCycleGraph> get children;

factory LibraryCycleGraph([void Function(LibraryCycleGraphBuilder) updates]) =
_$LibraryCycleGraph;
LibraryCycleGraph._();

/// All subgraphs in the graph, including the root.
Iterable<LibraryCycleGraph> get transitiveGraphs {
final result = Set<LibraryCycleGraph>.identity();
final nextGraphs = [this];

while (nextGraphs.isNotEmpty) {
final graph = nextGraphs.removeLast();
if (result.add(graph)) {
nextGraphs.addAll(graph.children);
}
}

return result;
}

/// All assets in the graph, including the root.
// TODO(davidmorgan): for best performance the graph should usually stay as a
// graph rather than being expanded into an explicit set of nodes. So, remove
// uses of this. If in the end it's still needed, investigate if it needs to
// be optimized.
Iterable<AssetId> get transitiveDeps sync* {
for (final graph in transitiveGraphs) {
yield* graph.root.ids;
}
}
}
Loading