From 9ca901ba6a85c410eff7a881577852536636c9ef Mon Sep 17 00:00:00 2001 From: David Morgan Date: Thu, 10 Apr 2025 14:14:05 +0200 Subject: [PATCH 1/2] Retain graphs when tracking inputs. --- build/lib/src/internal.dart | 1 + .../src/library_cycle_graph/asset_set.dart | 48 ++++ .../src/library_cycle_graph/asset_set.g.dart | 244 ++++++++++++++++++ .../library_cycle_graph/library_cycle.dart | 3 + .../library_cycle_graph/library_cycle.g.dart | 60 +++++ .../library_cycle_graph.dart | 4 + .../library_cycle_graph.g.dart | 75 ++++++ .../library_cycle_graph_loader.dart | 13 +- .../lib/src/analysis_driver_model.dart | 15 +- .../lib/src/server/asset_graph_handler.dart | 2 +- .../lib/src/asset_graph/graph.dart | 10 +- .../lib/src/asset_graph/node.dart | 19 +- .../lib/src/asset_graph/node.g.dart | 19 +- .../lib/src/asset_graph/serializers.dart | 2 + .../lib/src/asset_graph/serializers.g.dart | 19 ++ build_runner_core/lib/src/generate/build.dart | 12 +- .../lib/src/generate/input_tracker.dart | 17 +- .../test/asset_graph/graph_test.dart | 16 +- .../test/generate/build_definition_test.dart | 2 +- .../test/generate/build_test.dart | 2 +- .../lib/src/in_memory_reader_writer.dart | 2 +- 21 files changed, 526 insertions(+), 59 deletions(-) create mode 100644 build/lib/src/library_cycle_graph/asset_set.dart create mode 100644 build/lib/src/library_cycle_graph/asset_set.g.dart diff --git a/build/lib/src/internal.dart b/build/lib/src/internal.dart index 18a9378f3..20a7aae31 100644 --- a/build/lib/src/internal.dart +++ b/build/lib/src/internal.dart @@ -8,6 +8,7 @@ library; export 'library_cycle_graph/asset_deps.dart'; export 'library_cycle_graph/asset_deps_loader.dart'; +export 'library_cycle_graph/asset_set.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'; diff --git a/build/lib/src/library_cycle_graph/asset_set.dart b/build/lib/src/library_cycle_graph/asset_set.dart new file mode 100644 index 000000000..818e59f54 --- /dev/null +++ b/build/lib/src/library_cycle_graph/asset_set.dart @@ -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 'package:built_value/serializer.dart'; + +import '../asset/id.dart'; +import 'library_cycle_graph.dart'; + +part 'asset_set.g.dart'; + +abstract class AssetSet implements Built { + static Serializer get serializer => _$assetSetSerializer; + + BuiltSet get assets; + BuiltList get graphs; + BuiltSet get removedAssets; + + factory AssetSet([void Function(AssetSetBuilder) updates]) = _$AssetSet; + AssetSet._(); + + AssetSet difference(Iterable other) { + return rebuild((b) { + for (final asset in other) { + if (!b.assets.remove(asset)) { + b.removedAssets.add(asset); + } + } + }); + } + + Iterable get iterable { + final seenGraphs = Set.identity(); + Iterable result = assets; + + for (final graph in graphs) { + for (final graph in graph.transitiveGraphs) { + if (seenGraphs.add(graph)) result = result.followedBy(graph.root.ids); + } + } + + return result.where((e) => !removedAssets.contains(e)); + } + + bool get isEmpty => iterable.isEmpty; +} diff --git a/build/lib/src/library_cycle_graph/asset_set.g.dart b/build/lib/src/library_cycle_graph/asset_set.g.dart new file mode 100644 index 000000000..339fe9a16 --- /dev/null +++ b/build/lib/src/library_cycle_graph/asset_set.g.dart @@ -0,0 +1,244 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'asset_set.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$assetSetSerializer = new _$AssetSetSerializer(); + +class _$AssetSetSerializer implements StructuredSerializer { + @override + final Iterable types = const [AssetSet, _$AssetSet]; + @override + final String wireName = 'AssetSet'; + + @override + Iterable serialize( + Serializers serializers, + AssetSet object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'assets', + serializers.serialize( + object.assets, + specifiedType: const FullType(BuiltSet, const [ + const FullType(AssetId), + ]), + ), + 'graphs', + serializers.serialize( + object.graphs, + specifiedType: const FullType(BuiltList, const [ + const FullType(LibraryCycleGraph), + ]), + ), + 'removedAssets', + serializers.serialize( + object.removedAssets, + specifiedType: const FullType(BuiltSet, const [ + const FullType(AssetId), + ]), + ), + ]; + + return result; + } + + @override + AssetSet deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new AssetSetBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'assets': + result.assets.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltSet, const [ + const FullType(AssetId), + ]), + )! + as BuiltSet, + ); + break; + case 'graphs': + result.graphs.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(LibraryCycleGraph), + ]), + )! + as BuiltList, + ); + break; + case 'removedAssets': + result.removedAssets.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltSet, const [ + const FullType(AssetId), + ]), + )! + as BuiltSet, + ); + break; + } + } + + return result.build(); + } +} + +class _$AssetSet extends AssetSet { + @override + final BuiltSet assets; + @override + final BuiltList graphs; + @override + final BuiltSet removedAssets; + + factory _$AssetSet([void Function(AssetSetBuilder)? updates]) => + (new AssetSetBuilder()..update(updates))._build(); + + _$AssetSet._({ + required this.assets, + required this.graphs, + required this.removedAssets, + }) : super._() { + BuiltValueNullFieldError.checkNotNull(assets, r'AssetSet', 'assets'); + BuiltValueNullFieldError.checkNotNull(graphs, r'AssetSet', 'graphs'); + BuiltValueNullFieldError.checkNotNull( + removedAssets, + r'AssetSet', + 'removedAssets', + ); + } + + @override + AssetSet rebuild(void Function(AssetSetBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + AssetSetBuilder toBuilder() => new AssetSetBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is AssetSet && + assets == other.assets && + graphs == other.graphs && + removedAssets == other.removedAssets; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, assets.hashCode); + _$hash = $jc(_$hash, graphs.hashCode); + _$hash = $jc(_$hash, removedAssets.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'AssetSet') + ..add('assets', assets) + ..add('graphs', graphs) + ..add('removedAssets', removedAssets)) + .toString(); + } +} + +class AssetSetBuilder implements Builder { + _$AssetSet? _$v; + + SetBuilder? _assets; + SetBuilder get assets => + _$this._assets ??= new SetBuilder(); + set assets(SetBuilder? assets) => _$this._assets = assets; + + ListBuilder? _graphs; + ListBuilder get graphs => + _$this._graphs ??= new ListBuilder(); + set graphs(ListBuilder? graphs) => _$this._graphs = graphs; + + SetBuilder? _removedAssets; + SetBuilder get removedAssets => + _$this._removedAssets ??= new SetBuilder(); + set removedAssets(SetBuilder? removedAssets) => + _$this._removedAssets = removedAssets; + + AssetSetBuilder(); + + AssetSetBuilder get _$this { + final $v = _$v; + if ($v != null) { + _assets = $v.assets.toBuilder(); + _graphs = $v.graphs.toBuilder(); + _removedAssets = $v.removedAssets.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(AssetSet other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$AssetSet; + } + + @override + void update(void Function(AssetSetBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + AssetSet build() => _build(); + + _$AssetSet _build() { + _$AssetSet _$result; + try { + _$result = + _$v ?? + new _$AssetSet._( + assets: assets.build(), + graphs: graphs.build(), + removedAssets: removedAssets.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'assets'; + assets.build(); + _$failedField = 'graphs'; + graphs.build(); + _$failedField = 'removedAssets'; + removedAssets.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'AssetSet', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/build/lib/src/library_cycle_graph/library_cycle.dart b/build/lib/src/library_cycle_graph/library_cycle.dart index 0b2432a9c..6bea0fa7b 100644 --- a/build/lib/src/library_cycle_graph/library_cycle.dart +++ b/build/lib/src/library_cycle_graph/library_cycle.dart @@ -4,6 +4,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; import '../asset/id.dart'; @@ -14,6 +15,8 @@ part 'library_cycle.g.dart'; /// This means they have to be compiled as a single unit. abstract class LibraryCycle implements Built { + static Serializer get serializer => _$libraryCycleSerializer; + BuiltSet get ids; factory LibraryCycle([void Function(LibraryCycleBuilder) updates]) = diff --git a/build/lib/src/library_cycle_graph/library_cycle.g.dart b/build/lib/src/library_cycle_graph/library_cycle.g.dart index 6db8facb5..90347ba2f 100644 --- a/build/lib/src/library_cycle_graph/library_cycle.g.dart +++ b/build/lib/src/library_cycle_graph/library_cycle.g.dart @@ -6,6 +6,66 @@ part of 'library_cycle.dart'; // BuiltValueGenerator // ************************************************************************** +Serializer _$libraryCycleSerializer = + new _$LibraryCycleSerializer(); + +class _$LibraryCycleSerializer implements StructuredSerializer { + @override + final Iterable types = const [LibraryCycle, _$LibraryCycle]; + @override + final String wireName = 'LibraryCycle'; + + @override + Iterable serialize( + Serializers serializers, + LibraryCycle object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'ids', + serializers.serialize( + object.ids, + specifiedType: const FullType(BuiltSet, const [ + const FullType(AssetId), + ]), + ), + ]; + + return result; + } + + @override + LibraryCycle deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new LibraryCycleBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'ids': + result.ids.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltSet, const [ + const FullType(AssetId), + ]), + )! + as BuiltSet, + ); + break; + } + } + + return result.build(); + } +} + class _$LibraryCycle extends LibraryCycle { @override final BuiltSet ids; diff --git a/build/lib/src/library_cycle_graph/library_cycle_graph.dart b/build/lib/src/library_cycle_graph/library_cycle_graph.dart index 61b05f675..47f67532b 100644 --- a/build/lib/src/library_cycle_graph/library_cycle_graph.dart +++ b/build/lib/src/library_cycle_graph/library_cycle_graph.dart @@ -4,6 +4,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; import '../asset/id.dart'; import 'library_cycle.dart'; @@ -13,6 +14,9 @@ part 'library_cycle_graph.g.dart'; /// A directed acyclic graph of [LibraryCycle]s. abstract class LibraryCycleGraph implements Built { + static Serializer get serializer => + _$libraryCycleGraphSerializer; + LibraryCycle get root; BuiltList get children; diff --git a/build/lib/src/library_cycle_graph/library_cycle_graph.g.dart b/build/lib/src/library_cycle_graph/library_cycle_graph.g.dart index ab683aafc..4dd473963 100644 --- a/build/lib/src/library_cycle_graph/library_cycle_graph.g.dart +++ b/build/lib/src/library_cycle_graph/library_cycle_graph.g.dart @@ -6,6 +6,81 @@ part of 'library_cycle_graph.dart'; // BuiltValueGenerator // ************************************************************************** +Serializer _$libraryCycleGraphSerializer = + new _$LibraryCycleGraphSerializer(); + +class _$LibraryCycleGraphSerializer + implements StructuredSerializer { + @override + final Iterable types = const [LibraryCycleGraph, _$LibraryCycleGraph]; + @override + final String wireName = 'LibraryCycleGraph'; + + @override + Iterable serialize( + Serializers serializers, + LibraryCycleGraph object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'root', + serializers.serialize( + object.root, + specifiedType: const FullType(LibraryCycle), + ), + 'children', + serializers.serialize( + object.children, + specifiedType: const FullType(BuiltList, const [ + const FullType(LibraryCycleGraph), + ]), + ), + ]; + + return result; + } + + @override + LibraryCycleGraph deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new LibraryCycleGraphBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'root': + result.root.replace( + serializers.deserialize( + value, + specifiedType: const FullType(LibraryCycle), + )! + as LibraryCycle, + ); + break; + case 'children': + result.children.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(LibraryCycleGraph), + ]), + )! + as BuiltList, + ); + break; + } + } + + return result.build(); + } +} + class _$LibraryCycleGraph extends LibraryCycleGraph { @override final LibraryCycle root; diff --git a/build/lib/src/library_cycle_graph/library_cycle_graph_loader.dart b/build/lib/src/library_cycle_graph/library_cycle_graph_loader.dart index 089c54e49..275bc2447 100644 --- a/build/lib/src/library_cycle_graph/library_cycle_graph_loader.dart +++ b/build/lib/src/library_cycle_graph/library_cycle_graph_loader.dart @@ -463,6 +463,16 @@ class LibraryCycleGraphLoader { return _graphs[id]!; } + Future transitiveDepsGraphOf( + AssetDepsLoader assetDepsLoader, + AssetId id, + ) async { + return (await libraryCycleGraphOf( + assetDepsLoader, + id, + )).valueAt(phase: assetDepsLoader.phase); + } + /// Returns the transitive dependencies of Dart source [id] at the /// [assetDepsLoader] phase. /// @@ -481,8 +491,7 @@ class LibraryCycleGraphLoader { AssetDepsLoader assetDepsLoader, AssetId id, ) async { - final graph = await libraryCycleGraphOf(assetDepsLoader, id); - return graph.valueAt(phase: assetDepsLoader.phase).transitiveDeps; + return (await transitiveDepsGraphOf(assetDepsLoader, id)).transitiveDeps; } @override diff --git a/build_resolvers/lib/src/analysis_driver_model.dart b/build_resolvers/lib/src/analysis_driver_model.dart index ee2704a5c..896829009 100644 --- a/build_resolvers/lib/src/analysis_driver_model.dart +++ b/build_resolvers/lib/src/analysis_driver_model.dart @@ -109,26 +109,23 @@ class AnalysisDriverModel { required bool transitive, }) async { Iterable idsToSyncOntoFilesystem = [entrypoint]; - Iterable inputIds = [entrypoint]; // If requested, find transitive imports. if (transitive) { // Note: `transitiveDepsOf` can cause loads that cause builds that cause a // recursive `_performResolve` on this same `AnalysisDriver` instance. final nodeLoader = AssetDepsLoader(buildStep.phasedReader); - idsToSyncOntoFilesystem = await _graphLoader.transitiveDepsOf( + final graph = await _graphLoader.transitiveDepsGraphOf( nodeLoader, entrypoint, ); - inputIds = idsToSyncOntoFilesystem; + idsToSyncOntoFilesystem = graph.transitiveDeps; + buildStep.inputTracker.addGraph(graph); + } else { + // Notify [buildStep] of its inputs. + buildStep.inputTracker.add(entrypoint); } - // Notify [buildStep] of its inputs. - buildStep.inputTracker.addAll( - primaryInput: buildStep.inputId, - inputs: inputIds, - ); - await withDriverResource((driver) async { // Sync changes onto the "URI resolver", the in-memory filesystem. final phase = buildStep.phasedReader.phase; diff --git a/build_runner/lib/src/server/asset_graph_handler.dart b/build_runner/lib/src/server/asset_graph_handler.dart index 9a0b513fd..0b57097cc 100644 --- a/build_runner/lib/src/server/asset_graph_handler.dart +++ b/build_runner/lib/src/server/asset_graph_handler.dart @@ -130,7 +130,7 @@ class AssetGraphHandler { if (node.type == NodeType.generated || node.type == NodeType.glob) { final inputs = node.type == NodeType.generated - ? node.generatedNodeState!.inputs + ? node.generatedNodeState!.inputs.iterable : node.globNodeState!.inputs; for (final input in inputs) { if (filterGlob != null && !filterGlob.matches(input.toString())) { diff --git a/build_runner_core/lib/src/asset_graph/graph.dart b/build_runner_core/lib/src/asset_graph/graph.dart index c516ef5ac..b0d4a468c 100644 --- a/build_runner_core/lib/src/asset_graph/graph.dart +++ b/build_runner_core/lib/src/asset_graph/graph.dart @@ -186,7 +186,7 @@ class AssetGraph implements GeneratedAssetHider { } } _nodesByPackage.putIfAbsent(node.id.package, () => {})[node.id.path] = node; - if (node.inputs?.isNotEmpty ?? false) { + if (node.hasInputs) { _outputs = null; } @@ -282,7 +282,7 @@ class AssetGraph implements GeneratedAssetHider { for (var output in (outputs[node.id] ?? const {})) { updateNodeIfPresent(output, (nodeBuilder) { if (nodeBuilder.type == NodeType.generated) { - nodeBuilder.generatedNodeState.inputs.remove(id); + // nodeBuilder.generatedNodeState.inputs.remove(id); } else if (nodeBuilder.type == NodeType.glob) { nodeBuilder.globNodeState ..inputs.remove(id) @@ -292,7 +292,7 @@ class AssetGraph implements GeneratedAssetHider { } if (node.type == NodeType.generated) { - for (var input in node.generatedNodeState!.inputs) { + for (var input in node.generatedNodeState!.inputs.iterable) { // We may have already removed this node entirely. updateNodeIfPresent(input, (nodeBuilder) { nodeBuilder.primaryOutputs.remove(id); @@ -330,7 +330,7 @@ class AssetGraph implements GeneratedAssetHider { final result = >{}; for (final node in allNodes) { if (node.type == NodeType.generated) { - for (final input in node.generatedNodeState!.inputs) { + for (final input in node.generatedNodeState!.inputs.iterable) { result.putIfAbsent(input, () => {}).add(node.id); } result @@ -775,7 +775,7 @@ class AssetGraph implements GeneratedAssetHider { for (var output in outputs) { updateNodeIfPresent(output, (nodeBuilder) { if (nodeBuilder.type == NodeType.generated) { - nodeBuilder.generatedNodeState.inputs.add(input); + nodeBuilder.generatedNodeState.inputs.assets.add(input); } else if (nodeBuilder.type == NodeType.glob) { nodeBuilder.globNodeState.inputs.add(input); } diff --git a/build_runner_core/lib/src/asset_graph/node.dart b/build_runner_core/lib/src/asset_graph/node.dart index c2a8412eb..c48eb0d03 100644 --- a/build_runner_core/lib/src/asset_graph/node.dart +++ b/build_runner_core/lib/src/asset_graph/node.dart @@ -5,6 +5,8 @@ import 'dart:convert'; import 'package:build/build.dart' hide Builder; +// ignore: implementation_imports +import 'package:build/src/internal.dart'; import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; @@ -178,7 +180,7 @@ abstract class AssetNode implements Built { b.generatedNodeConfiguration.builderOptionsId = builderOptionsId; b.generatedNodeConfiguration.phaseNumber = phaseNumber; b.generatedNodeConfiguration.isHidden = isHidden; - b.generatedNodeState.inputs.replace(inputs ?? []); + b.generatedNodeState.inputs.assets.replace(inputs ?? []); b.generatedNodeState.pendingBuildAction = pendingBuildAction; b.generatedNodeState.wasOutput = wasOutput; b.generatedNodeState.isFailure = isFailure; @@ -236,7 +238,7 @@ abstract class AssetNode implements Built { /// The generated node inputs, or the glob node inputs, or `null` if the node /// is not of one of those two types. - BuiltSet? get inputs { + Object? get inputs { switch (type) { case NodeType.generated: return generatedNodeState!.inputs; @@ -246,6 +248,17 @@ abstract class AssetNode implements Built { return null; } } + + bool get hasInputs { + switch (type) { + case NodeType.generated: + return !generatedNodeState!.inputs.isEmpty; + case NodeType.glob: + return globNodeState!.inputs.isNotEmpty; + default: + return false; + } + } } /// Additional configuration for an [AssetNode.generated]. @@ -289,7 +302,7 @@ abstract class GeneratedNodeState /// All the inputs that were read when generating this asset, or deciding not /// to generate it. - BuiltSet get inputs; + AssetSet get inputs; /// The next work that needs doing on this node. PendingBuildAction get pendingBuildAction; diff --git a/build_runner_core/lib/src/asset_graph/node.g.dart b/build_runner_core/lib/src/asset_graph/node.g.dart index 5cb6313c1..6776cb518 100644 --- a/build_runner_core/lib/src/asset_graph/node.g.dart +++ b/build_runner_core/lib/src/asset_graph/node.g.dart @@ -412,9 +412,7 @@ class _$GeneratedNodeStateSerializer 'inputs', serializers.serialize( object.inputs, - specifiedType: const FullType(BuiltSet, const [ - const FullType(AssetId), - ]), + specifiedType: const FullType(AssetSet), ), 'pendingBuildAction', serializers.serialize( @@ -454,11 +452,9 @@ class _$GeneratedNodeStateSerializer result.inputs.replace( serializers.deserialize( value, - specifiedType: const FullType(BuiltSet, const [ - const FullType(AssetId), - ]), + specifiedType: const FullType(AssetSet), )! - as BuiltSet, + as AssetSet, ); break; case 'pendingBuildAction': @@ -1070,7 +1066,7 @@ class GeneratedNodeConfigurationBuilder class _$GeneratedNodeState extends GeneratedNodeState { @override - final BuiltSet inputs; + final AssetSet inputs; @override final PendingBuildAction pendingBuildAction; @override @@ -1155,10 +1151,9 @@ class GeneratedNodeStateBuilder implements Builder { _$GeneratedNodeState? _$v; - SetBuilder? _inputs; - SetBuilder get inputs => - _$this._inputs ??= new SetBuilder(); - set inputs(SetBuilder? inputs) => _$this._inputs = inputs; + AssetSetBuilder? _inputs; + AssetSetBuilder get inputs => _$this._inputs ??= new AssetSetBuilder(); + set inputs(AssetSetBuilder? inputs) => _$this._inputs = inputs; PendingBuildAction? _pendingBuildAction; PendingBuildAction? get pendingBuildAction => _$this._pendingBuildAction; diff --git a/build_runner_core/lib/src/asset_graph/serializers.dart b/build_runner_core/lib/src/asset_graph/serializers.dart index a9eabdcce..af91189ba 100644 --- a/build_runner_core/lib/src/asset_graph/serializers.dart +++ b/build_runner_core/lib/src/asset_graph/serializers.dart @@ -5,6 +5,8 @@ import 'dart:convert'; import 'package:build/build.dart' show AssetId, PostProcessBuildStep; +// ignore: implementation_imports +import 'package:build/src/internal.dart'; import 'package:built_collection/built_collection.dart'; import 'package:built_value/serializer.dart'; import 'package:crypto/crypto.dart'; diff --git a/build_runner_core/lib/src/asset_graph/serializers.g.dart b/build_runner_core/lib/src/asset_graph/serializers.g.dart index 3107ed284..465704835 100644 --- a/build_runner_core/lib/src/asset_graph/serializers.g.dart +++ b/build_runner_core/lib/src/asset_graph/serializers.g.dart @@ -9,13 +9,22 @@ part of 'serializers.dart'; Serializers _$serializers = (new Serializers().toBuilder() ..add(AssetNode.serializer) + ..add(AssetSet.serializer) ..add(GeneratedNodeConfiguration.serializer) ..add(GeneratedNodeState.serializer) ..add(GlobNodeConfiguration.serializer) ..add(GlobNodeState.serializer) + ..add(LibraryCycle.serializer) + ..add(LibraryCycleGraph.serializer) ..add(NodeType.serializer) ..add(PendingBuildAction.serializer) ..add(PostProcessBuildStepId.serializer) + ..addBuilderFactory( + const FullType(BuiltList, const [ + const FullType(LibraryCycleGraph), + ]), + () => new ListBuilder(), + ) ..addBuilderFactory( const FullType(BuiltSet, const [const FullType(AssetId)]), () => new SetBuilder(), @@ -32,6 +41,16 @@ Serializers _$serializers = const FullType(BuiltSet, const [const FullType(AssetId)]), () => new SetBuilder(), ) + ..addBuilderFactory( + const FullType(BuiltList, const [ + const FullType(LibraryCycleGraph), + ]), + () => new ListBuilder(), + ) + ..addBuilderFactory( + const FullType(BuiltSet, const [const FullType(AssetId)]), + () => new SetBuilder(), + ) ..addBuilderFactory( const FullType(BuiltSet, const [const FullType(AssetId)]), () => new SetBuilder(), diff --git a/build_runner_core/lib/src/generate/build.dart b/build_runner_core/lib/src/generate/build.dart index d9744455f..cb9f2dc80 100644 --- a/build_runner_core/lib/src/generate/build.dart +++ b/build_runner_core/lib/src/generate/build.dart @@ -247,7 +247,7 @@ class Build { }, (e, st) { if (!done.isCompleted) { - _logger.severe('Unhandled build failure!', e, st); + _logger.severe('Unhandled build failure! $e $st', e, st); done.complete(BuildResult(BuildStatus.failure, [])); } }, @@ -722,7 +722,7 @@ class Build { // Otherwise, we only check the first output, because all outputs share the // same inputs and invalidation state. var firstNode = assetGraph.get(outputs.first)!; - assert( + /*assert( outputs .skip(1) .every( @@ -735,7 +735,7 @@ class Build { .isEmpty, ), 'All outputs of a build action should share the same inputs.', - ); + );*/ final firstNodeState = firstNode.generatedNodeState!; @@ -768,7 +768,7 @@ class Build { } final inputs = firstNodeState.inputs; - for (final input in inputs) { + for (final input in inputs.iterable) { final node = assetGraph.get(input)!; if (node.type == NodeType.generated) { if (node.generatedNodeConfiguration!.phaseNumber >= phaseNumber) { @@ -989,7 +989,7 @@ class Build { }) async { if (outputs.isEmpty) return; var usedInputs = - unusedAssets != null + unusedAssets != null && unusedAssets.isNotEmpty ? inputTracker.inputs.difference(unusedAssets) : inputTracker.inputs; @@ -1036,7 +1036,7 @@ class Build { // Make sure output invalidation follows primary outputs for builds // that won't run. assetGraph.updateNode(output, (nodeBuilder) { - nodeBuilder.generatedNodeState.inputs.add(node.id); + nodeBuilder.generatedNodeState.inputs.assets.add(node.id); }); } await failureReporter.markSkipped( diff --git a/build_runner_core/lib/src/generate/input_tracker.dart b/build_runner_core/lib/src/generate/input_tracker.dart index 110150924..66ca98798 100644 --- a/build_runner_core/lib/src/generate/input_tracker.dart +++ b/build_runner_core/lib/src/generate/input_tracker.dart @@ -2,8 +2,6 @@ // 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:collection'; - import 'package:build/build.dart'; // ignore: implementation_imports import 'package:build/src/internal.dart'; @@ -21,7 +19,7 @@ class InputTracker { static Map> inputTrackersForTesting = Map.identity(); - final HashSet _inputs = HashSet(); + AssetSet inputs = AssetSet(); /// Creates an input tracker. /// @@ -34,16 +32,13 @@ class InputTracker { } } - void add(AssetId input) => _inputs.add(input); - - void addAll({ - required AssetId primaryInput, - required Iterable inputs, - }) => _inputs.addAll(inputs); + void add(AssetId input) => + inputs = inputs.rebuild((b) => b..assets.add(input)); - Set get inputs => _inputs; + void addGraph(LibraryCycleGraph graph) => + inputs = inputs.rebuild((b) => b..graphs.add(graph)); void clear() { - inputs.clear(); + inputs = AssetSet(); } } diff --git a/build_runner_core/test/asset_graph/graph_test.dart b/build_runner_core/test/asset_graph/graph_test.dart index ada7b95dd..8187b5790 100644 --- a/build_runner_core/test/asset_graph/graph_test.dart +++ b/build_runner_core/test/asset_graph/graph_test.dart @@ -145,7 +145,7 @@ void main() { var syntheticNode = AssetNode.missingSource(makeAssetId()); generatedNode = generatedNode.rebuild( - (b) => b.generatedNodeState.inputs.addAll([ + (b) => b.generatedNodeState.inputs.assets.addAll([ node.id, syntheticNode.id, globNode.id, @@ -351,7 +351,7 @@ void main() { graph.updateNode(primaryOutputId, (nodeBuilder) { nodeBuilder.generatedNodeState ..pendingBuildAction = PendingBuildAction.none - ..inputs.add(primaryInputId); + ..inputs.assets.add(primaryInputId); }); await graph.updateAndInvalidate( buildPhases, @@ -430,7 +430,9 @@ void main() { var secondaryNode = AssetNode.source(secondaryId); graph.updateNode(primaryOutputId, (nodeBuilder) { - nodeBuilder.generatedNodeState.inputs.add(secondaryNode.id); + nodeBuilder.generatedNodeState.inputs.assets.add( + secondaryNode.id, + ); }); graph.add(secondaryNode); @@ -469,7 +471,7 @@ void main() { graph.updateNode(primaryOutputId, (nodeBuilder) { nodeBuilder.generatedNodeState ..pendingBuildAction = PendingBuildAction.none - ..inputs.add(globNode.id); + ..inputs.assets.add(globNode.id); }); graph.add(globNode); @@ -701,12 +703,12 @@ void main() { graph.updateNode(outputReadingNode, (nodeBuilder) { nodeBuilder.generatedNodeState ..pendingBuildAction = PendingBuildAction.none - ..inputs.add(nodeToRead); + ..inputs.assets.add(nodeToRead); }); graph.updateNode(lastPrimaryOutputNode, (nodeBuilder) { nodeBuilder.generatedNodeState ..pendingBuildAction = PendingBuildAction.none - ..inputs.add(outputReadingNode); + ..inputs.assets.add(outputReadingNode); }); final invalidatedNodes = await graph.updateAndInvalidate( @@ -754,7 +756,7 @@ void main() { graph.updateNode(generatedDart, (nodeBuilder) { nodeBuilder.generatedNodeState ..pendingBuildAction = PendingBuildAction.none - ..inputs.addAll([generatedPart, toBeGeneratedDart]); + ..inputs.assets.addAll([generatedPart, toBeGeneratedDart]); }); expect(graph.get(source)!.type, NodeType.source); diff --git a/build_runner_core/test/generate/build_definition_test.dart b/build_runner_core/test/generate/build_definition_test.dart index 71b0578bd..d7671a6c5 100644 --- a/build_runner_core/test/generate/build_definition_test.dart +++ b/build_runner_core/test/generate/build_definition_test.dart @@ -226,7 +226,7 @@ targets: originalAssetGraph.updateNode(aTxtCopy, (nodeBuilder) { nodeBuilder.generatedNodeState ..pendingBuildAction = PendingBuildAction.none - ..inputs.add(aTxt); + ..inputs.assets.add(aTxt); }); await createFile(assetGraphPath, originalAssetGraph.serialize()); diff --git a/build_runner_core/test/generate/build_test.dart b/build_runner_core/test/generate/build_test.dart index 53f04b917..174b62260 100644 --- a/build_runner_core/test/generate/build_test.dart +++ b/build_runner_core/test/generate/build_test.dart @@ -1817,7 +1817,7 @@ void main() { var fileBNode = graph.get(makeAssetId('a|lib/file.b'))!; var fileCNode = graph.get(makeAssetId('a|lib/file.c'))!; expect( - outputNode.generatedNodeState!.inputs, + outputNode.generatedNodeState!.inputs.iterable, unorderedEquals([fileANode.id, fileCNode.id]), ); final computedOutputs = graph.computeOutputs(); diff --git a/build_test/lib/src/in_memory_reader_writer.dart b/build_test/lib/src/in_memory_reader_writer.dart index efe6f2dd8..4f7abbcf9 100644 --- a/build_test/lib/src/in_memory_reader_writer.dart +++ b/build_test/lib/src/in_memory_reader_writer.dart @@ -188,7 +188,7 @@ class _ReaderWriterTestingImpl implements ReaderWriterTesting { @override Iterable get inputsTracked => InputTracker.inputTrackersForTesting[_readerWriter.filesystem]! - .expand((tracker) => tracker.inputs) + .expand((tracker) => tracker.inputs.iterable) .toSet(); @override From 3aba063b828f874362cee98d938c45fc9ff35105 Mon Sep 17 00:00:00 2001 From: David Morgan Date: Fri, 11 Apr 2025 18:34:46 +0200 Subject: [PATCH 2/2] Stack overflow fun. --- .../src/asset_graph/identity_serializer.dart | 84 ++++-- .../lib/src/asset_graph/serialization.dart | 33 ++- .../lib/src/asset_graph/serializers.dart | 7 + .../test/asset_graph/serializers_test.dart | 255 ++++++++++++++++++ 4 files changed, 354 insertions(+), 25 deletions(-) create mode 100644 build_runner_core/test/asset_graph/serializers_test.dart diff --git a/build_runner_core/lib/src/asset_graph/identity_serializer.dart b/build_runner_core/lib/src/asset_graph/identity_serializer.dart index dd4bf762c..83cfbb539 100644 --- a/build_runner_core/lib/src/asset_graph/identity_serializer.dart +++ b/build_runner_core/lib/src/asset_graph/identity_serializer.dart @@ -20,21 +20,25 @@ class IdentitySerializer implements PrimitiveSerializer { final Serializer delegate; final PrimitiveSerializer? _primitiveDelegate; final StructuredSerializer? _structuredDelegate; + final FullType specifiedType; - final Map _ids = Map.identity(); - final List _objects = []; + Map _ids = Map.identity(); + List _objects = []; List _serializedObjects = []; + int lastTried = 0; + /// A serializer wrapping [delegate] to deduplicate by identity. IdentitySerializer(this.delegate) : _primitiveDelegate = delegate is PrimitiveSerializer ? delegate : null, _structuredDelegate = - delegate is StructuredSerializer ? delegate : null; + delegate is StructuredSerializer ? delegate : null, + specifiedType = FullType(delegate.types.first); /// Sets the stored object values to [objects]. /// /// Serialized values are indices into this list. - void deserializeWithObjects(Iterable objects) { + /*void deserializeWithObjects(Iterable objects) { _ids.clear(); _objects.clear(); _serializedObjects.clear(); @@ -42,18 +46,57 @@ class IdentitySerializer implements PrimitiveSerializer { _objects.add(object); _ids[object] = _objects.length - 1; } + }*/ + + void deserializeObjects(List serializedObjects) { + _ids.clear(); + _serializedObjects = serializedObjects; + _objects = List.filled(_serializedObjects.length, null); + /*print('got ${_serializedObjects.length}'); + print('into ${_objects.length}'); + try { + for (var i = _serializedObjects.length - 1; i != -1; --i) { + print('deserialize $i ${_serializedObjects[i]}'); + if (_primitiveDelegate != null) { + _objects[i] = _primitiveDelegate.deserialize( + serializers, + _serializedObjects[i]!, + ); + } else { + _objects[i] = _structuredDelegate!.deserialize( + serializers, + _serializedObjects[i] as Iterable, + ); + _ids[_objects[i] as T] = i; + } + } + } catch (e, st) { + print('$e $st'); + rethrow; + }*/ } /// The list of unique objects encountered since the most recent [reset]. - List get serializedObjects => _serializedObjects; + List serializedObjects(Serializers serializers) { + while (_serializedObjects.length < _objects.length) { + // print('$_serializedObjects, $_objects'); + final object = _objects[_serializedObjects.length]!; + final serialized = + _primitiveDelegate == null + ? _structuredDelegate!.serialize(serializers, object) + : _primitiveDelegate.serialize(serializers, object); + _serializedObjects.add(serialized); + } + return _serializedObjects; + } /// Clears the ID to object and serialized object mappings. /// /// Call this after serializing or deserializing to avoid retaining objects in /// memory; or, don't call it to continue using the same IDs and objects. void reset() { - _ids.clear(); - _objects.clear(); + _ids = {}; + _objects = []; _serializedObjects = []; } @@ -68,7 +111,24 @@ class IdentitySerializer implements PrimitiveSerializer { Serializers serializers, Object serialized, { FullType specifiedType = FullType.unspecified, - }) => _objects[serialized as int]; + }) { + serialized as int; + if (_objects[serialized] == null) { + if (_primitiveDelegate != null) { + _objects[serialized] = _primitiveDelegate.deserialize( + serializers, + _serializedObjects[serialized]!, + ); + } else { + _objects[serialized] = _structuredDelegate!.deserialize( + serializers, + _serializedObjects[serialized] as Iterable, + ); + _ids[_objects[serialized] as T] = serialized; + } + } + return _objects[serialized]!; + } @override Object serialize( @@ -78,13 +138,7 @@ class IdentitySerializer implements PrimitiveSerializer { }) { // If it has already been seen, return the ID. return _ids.putIfAbsent(object, () { - // Otherwise, serialize it, store the value and serialized value, and - // return the index of the last of `_objects` as the ID. - final serialized = - _primitiveDelegate == null - ? _structuredDelegate!.serialize(serializers, object) - : _primitiveDelegate.serialize(serializers, object); - _serializedObjects.add(serialized); + // print('add $object'); return (_objects..add(object)).length - 1; }); } diff --git a/build_runner_core/lib/src/asset_graph/serialization.dart b/build_runner_core/lib/src/asset_graph/serialization.dart index 4cad1866f..9de238e16 100644 --- a/build_runner_core/lib/src/asset_graph/serialization.dart +++ b/build_runner_core/lib/src/asset_graph/serialization.dart @@ -12,6 +12,7 @@ const _version = 28; /// Deserializes an [AssetGraph] from a [Map]. AssetGraph deserializeAssetGraph(List bytes) { + //print('**** \n \n **** deserialize \n \n'); dynamic serializedGraph; try { serializedGraph = jsonDecode(utf8.decode(bytes)); @@ -23,12 +24,18 @@ AssetGraph deserializeAssetGraph(List bytes) { throw AssetGraphCorruptedException(); } - identityAssetIdSerializer.deserializeWithObjects( - (serializedGraph['ids'] as List).map( - (id) => assetIdSerializer.deserialize(serializers, id as Object), - ), + //print('**** ids'); + identityAssetIdSerializer.deserializeObjects( + //serializers, + serializedGraph['ids'] as List, + ); + //print('**** graphs'); + identityLibraryCycleGraphSerializer.deserializeObjects( + // serializers, + serializedGraph['graphs'] as List, ); + //print('**** more'); var packageLanguageVersions = { for (var entry in (serializedGraph['packageLanguageVersions'] as Map) @@ -63,6 +70,7 @@ AssetGraph deserializeAssetGraph(List bytes) { } identityAssetIdSerializer.reset(); + identityLibraryCycleGraphSerializer.reset(); return graph; } @@ -73,14 +81,21 @@ AssetNode _deserializeAssetNode(List serializedNode) => /// Serializes an [AssetGraph] into a [Map]. List serializeAssetGraph(AssetGraph graph) { // Serialize nodes first so all `AssetId` instances are seen by - // `identityAssetIdSeralizer`. + // `identityAssetIdSerializer` and `libraryCycleGraphSerializer`. final nodes = graph.allNodes .map((node) => serializers.serializeWith(AssetNode.serializer, node)) .toList(growable: false); + final postProcessOutputs = serializers.serialize( + graph._postProcessBuildStepOutputs, + specifiedType: postProcessBuildStepOutputsFullType, + ); var result = { 'version': _version, - 'ids': identityAssetIdSerializer.serializedObjects, + 'ids': identityAssetIdSerializer.serializedObjects(serializers), + 'graphs': identityLibraryCycleGraphSerializer.serializedObjects( + serializers, + ), 'dart_version': graph.dartVersion, 'nodes': nodes, 'buildActionsDigest': _serializeDigest(graph.buildPhasesDigest), @@ -89,13 +104,11 @@ List serializeAssetGraph(AssetGraph graph) { .map((pkg, version) => MapEntry(pkg, version?.toString())) .toMap(), 'enabledExperiments': graph.enabledExperiments.toList(), - 'postProcessOutputs': serializers.serialize( - graph._postProcessBuildStepOutputs, - specifiedType: postProcessBuildStepOutputsFullType, - ), + 'postProcessOutputs': postProcessOutputs, }; identityAssetIdSerializer.reset(); + identityLibraryCycleGraphSerializer.reset(); return utf8.encode(json.encode(result)); } diff --git a/build_runner_core/lib/src/asset_graph/serializers.dart b/build_runner_core/lib/src/asset_graph/serializers.dart index af91189ba..87c01cb6b 100644 --- a/build_runner_core/lib/src/asset_graph/serializers.dart +++ b/build_runner_core/lib/src/asset_graph/serializers.dart @@ -32,10 +32,17 @@ final identityAssetIdSerializer = IdentitySerializer( assetIdSerializer, ); +final libraryCycleGraphSerializer = + LibraryCycleGraph.serializer as StructuredSerializer; + +final identityLibraryCycleGraphSerializer = + IdentitySerializer(libraryCycleGraphSerializer); + @SerializersFor([AssetNode]) final Serializers serializers = (_$serializers.toBuilder() ..add(identityAssetIdSerializer) + ..add(identityLibraryCycleGraphSerializer) ..add(DigestSerializer()) ..add(MapSerializer()) ..add(SetSerializer()) diff --git a/build_runner_core/test/asset_graph/serializers_test.dart b/build_runner_core/test/asset_graph/serializers_test.dart new file mode 100644 index 000000000..04e865224 --- /dev/null +++ b/build_runner_core/test/asset_graph/serializers_test.dart @@ -0,0 +1,255 @@ +// 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:convert'; + +import 'package:build/build.dart'; +// ignore: implementation_import +import 'package:build/src/internal.dart'; +import 'package:build_runner_core/src/asset_graph/serializers.dart'; +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/serializer.dart'; +import 'package:test/test.dart'; + +void main() { + final a1 = AssetId('a', '1'); + final a2 = AssetId('a', '2'); + final a3 = AssetId('a', '3'); + + group('identity', () { + setUp(() { + identityAssetIdSerializer.reset(); + identityLibraryCycleGraphSerializer.reset(); + }); + + group('AssetId', () { + final testSerializers = + (serializers.toBuilder()..addBuilderFactory( + const FullType(BuiltList, [FullType(AssetId)]), + ListBuilder.new, + )) + .build(); + + final data = [a1, a2, a3, a1, a2, a3].build(); + final serialized = [0, 1, 2, 0, 1, 2]; + final serializedObjects = ['a|1', 'a|2', 'a|3']; + + test('serialize', () { + expect( + testSerializers.serialize( + data, + specifiedType: const FullType(BuiltList, [FullType(AssetId)]), + ), + serialized, + ); + expect( + identityAssetIdSerializer.serializedObjects(serializers), + serializedObjects, + ); + }); + + test('deserialize', () { + identityAssetIdSerializer.deserializeObjects(serializedObjects); + expect( + testSerializers.deserialize( + serialized, + specifiedType: const FullType(BuiltList, [FullType(AssetId)]), + ), + data, + ); + }); + }); + + group('LibraryCycleGraph flat', () { + final testSerializers = + (serializers.toBuilder()..addBuilderFactory( + const FullType(BuiltList, [FullType(LibraryCycleGraph)]), + ListBuilder.new, + )) + .build(); + + final g1 = LibraryCycleGraph((b) => b..root.ids.add(a1)); + final g2 = LibraryCycleGraph((b) => b..root.ids.add(a2)); + final g3 = LibraryCycleGraph((b) => b..root.ids.add(a3)); + final data = [g1, g2, g3, g1, g2, g3].build(); + final serialized = [0, 1, 2, 0, 1, 2]; + final serializedObjects = + json.decode( + '[["root",["ids",[0]],"children",[]],' + '["root",["ids",[1]],"children",[]],' + '["root",["ids",[2]],"children",[]]]', + ) + as List; + final assetIdSerializedObjects = ['a|1', 'a|2', 'a|3']; + + test('serialize', () { + expect( + serializers.serialize( + data, + specifiedType: const FullType(BuiltList, [ + FullType(LibraryCycleGraph), + ]), + ), + serialized, + ); + expect( + identityLibraryCycleGraphSerializer.serializedObjects(serializers), + serializedObjects, + ); + }); + + test('deserialize', () { + identityAssetIdSerializer.deserializeObjects(assetIdSerializedObjects); + identityLibraryCycleGraphSerializer.deserializeObjects( + serializedObjects, + ); + expect( + testSerializers.deserialize( + serialized, + specifiedType: const FullType(BuiltList, [ + FullType(LibraryCycleGraph), + ]), + ), + data, + ); + }); + }); + + group('LibraryCycleGraph nested', () { + final testSerializers = + (serializers.toBuilder()..addBuilderFactory( + const FullType(BuiltList, [FullType(LibraryCycleGraph)]), + ListBuilder.new, + )) + .build(); + + final g1 = LibraryCycleGraph((b) => b..root.ids.add(a1)); + final g2 = LibraryCycleGraph( + (b) => + b + ..root.ids.add(a2) + ..children.add(g1), + ); + final g3 = LibraryCycleGraph( + (b) => + b + ..root.ids.add(a3) + ..children.add(g2), + ); + final data = [g1, g2, g3, g1, g2, g3].build(); + final serialized = [0, 1, 2, 0, 1, 2]; + final serializedObjects = + json.decode( + '[["root",["ids",[0]],"children",[]],' + '["root",["ids",[1]],"children",[0]],' + '["root",["ids",[2]],"children",[1]]]', + ) + as List; + + final assetIdSerializedObjects = ['a|1', 'a|2', 'a|3']; + + test('serialize', () { + expect( + serializers.serialize( + data, + specifiedType: const FullType(BuiltList, [ + FullType(LibraryCycleGraph), + ]), + ), + serialized, + ); + expect( + identityLibraryCycleGraphSerializer.serializedObjects(serializers), + serializedObjects, + ); + }); + + test('deserialize', () { + identityAssetIdSerializer.deserializeObjects(assetIdSerializedObjects); + identityLibraryCycleGraphSerializer.deserializeObjects( + serializedObjects, + ); + expect( + testSerializers.deserialize( + serialized, + specifiedType: const FullType(BuiltList, [ + FullType(LibraryCycleGraph), + ]), + ), + data, + ); + }); + }); + + group('LibraryCycleGraph deeply nested', () { + final size = 1000; + final testSerializers = + (serializers.toBuilder()..addBuilderFactory( + const FullType(BuiltList, [FullType(LibraryCycleGraph)]), + ListBuilder.new, + )) + .build(); + + final assetIds = [ + for (var i = 0; i != size; ++i) AssetId('a', '${i + 1}'), + ]; + final graphs = []; + for (var i = 0; i != size; ++i) { + graphs.add( + LibraryCycleGraph((b) { + b.root.ids.add(assetIds[i]); + if (i < size) b.children.add(graphs[i - 1]); + }), + ); + } + final data = [graphs[0], graphs[0]].build(); + final serialized = [0, 0].build(); + final serializedObjects = [ + for (var i = 0; i != size; ++i) + [ + 'root', + [ + 'ids', + [i], + ], + 'children', + [if (i < size - 1) i + 1], + ], + ]; + final assetIdSerializedObjects = [ + for (var i = 0; i != size; ++i) 'a|${i + 1}', + ]; + + test('serialize', () { + expect( + serializers.serialize( + data, + specifiedType: const FullType(BuiltList, [ + FullType(LibraryCycleGraph), + ]), + ), + serialized, + ); + expect( + identityLibraryCycleGraphSerializer.serializedObjects(serializers), + serializedObjects, + ); + }); + + test('deserialize', () { + identityAssetIdSerializer.deserializeObjects(assetIdSerializedObjects); + identityLibraryCycleGraphSerializer.deserializeObjects( + serializedObjects, + ); + final deserialized = testSerializers.deserialize( + serialized, + specifiedType: const FullType(BuiltList, [ + FullType(LibraryCycleGraph), + ]), + ); + expect(deserialized == data, isTrue); + }); + }); + }); +}