Skip to content

Commit

Permalink
Report changed assets through build daemon (#3273)
Browse files Browse the repository at this point in the history
Report a list of changed assets (outputs + changed inputs from watcher) through the build daemon. Because this list can be rather large, the changes are only reported to clients actually interested in them.

Closes #3268.
  • Loading branch information
simolus3 committed Mar 28, 2022
1 parent 02758c0 commit f82f3dd
Show file tree
Hide file tree
Showing 15 changed files with 241 additions and 42 deletions.
4 changes: 3 additions & 1 deletion build_daemon/CHANGELOG.md
@@ -1,5 +1,7 @@
## 3.0.3-dev
## 3.1.0-dev

- Add `BuildResults.changedAssets` containing asset URIs changed during a
build.
- Updated the example to use `dart pub` instead of `pub`.

## 3.0.2
Expand Down
14 changes: 14 additions & 0 deletions build_daemon/lib/data/build_status.dart
Expand Up @@ -6,6 +6,8 @@ import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';

import 'build_target.dart';

part 'build_status.g.dart';

class BuildStatus extends EnumClass {
Expand Down Expand Up @@ -59,4 +61,16 @@ abstract class BuildResults
BuildResults._();

BuiltList<BuildResult> get results;

/// A list of asset URIs that were modified since the last build.
///
/// This includes both sources that were updated and affected generated assets
/// that were rebuilt.
///
/// To avoid communication overhead for clients not interested in this field,
/// it is not set by default. To enable it, register at least one target with
/// [DefaultBuildTarget.reportChangedAssets].
/// However, build implementations can unconditionally set this field as it
/// is stripped out in the daemon server implementation.
BuiltList<Uri>? get changedAssets;
}
42 changes: 36 additions & 6 deletions build_daemon/lib/data/build_status.g.dart

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

13 changes: 13 additions & 0 deletions build_daemon/lib/data/build_target.dart
Expand Up @@ -2,6 +2,8 @@ import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';

import 'build_status.dart';

part 'build_target.g.dart';

/// The string representation of a build target, e.g. folder path.
Expand All @@ -16,6 +18,10 @@ abstract class DefaultBuildTarget
static Serializer<DefaultBuildTarget> get serializer =>
_$defaultBuildTargetSerializer;

@BuiltValueHook(initializeBuilder: true)
static void _setDefaults(DefaultBuildTargetBuilder b) =>
b.reportChangedAssets = false;

factory DefaultBuildTarget([void Function(DefaultBuildTargetBuilder) b]) =
_$DefaultBuildTarget;

Expand All @@ -38,6 +44,13 @@ abstract class DefaultBuildTarget
/// - package:*/**
/// - $target/**
BuiltSet<String>? get buildFilters;

/// Whether the [BuildResults] events emitted for this target should report a
/// list of assets invalidated in a build.
///
/// This defaults to `false` to reduce the serialization overhead when this
/// information is not required.
bool get reportChangedAssets;
}

/// The location to write the build outputs.
Expand Down
36 changes: 33 additions & 3 deletions build_daemon/lib/data/build_target.g.dart

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

3 changes: 3 additions & 0 deletions build_daemon/lib/data/serializers.g.dart

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

14 changes: 12 additions & 2 deletions build_daemon/lib/src/fakes/fake_test_builder.dart
Expand Up @@ -15,14 +15,16 @@ class FakeTestDaemonBuilder implements DaemonBuilder {
static final buildCompletedMessage = 'Build Completed';

final _outputStreamController = StreamController<ServerLog>();
final _buildsController = StreamController<BuildResults>.broadcast();

late final Stream<ServerLog> _logs;

FakeTestDaemonBuilder() {
_logs = _outputStreamController.stream.asBroadcastStream();
}

@override
Stream<BuildResults> get builds => Stream.empty();
Stream<BuildResults> get builds => _buildsController.stream;

@override
Stream<ServerLog> get logs => _logs;
Expand All @@ -34,8 +36,16 @@ class FakeTestDaemonBuilder implements DaemonBuilder {
..loggerName = loggerName
..level = Level.INFO
..message = buildCompletedMessage));

_buildsController.add(BuildResults(
(b) => b.changedAssets.add(Uri.parse('package:foo/bar.dart'))));
}

@override
Future<void> stop() => _outputStreamController.close();
Future<void> stop() {
return Future.wait([
_outputStreamController.close(),
_buildsController.close(),
]);
}
}
17 changes: 13 additions & 4 deletions build_daemon/lib/src/managers/build_target_manager.dart
Expand Up @@ -19,6 +19,7 @@ bool _shouldBuild(BuildTarget target, Iterable<WatchEvent> changes) =>
/// the Dart Build Daemon.
class BuildTargetManager {
var _buildTargets = <BuildTarget, Set<WebSocketChannel>>{};
final _channelSubscriptions = <WebSocketChannel, Set<BuildTarget>>{};

bool Function(BuildTarget, Iterable<WatchEvent>) shouldBuild;

Expand All @@ -37,16 +38,24 @@ class BuildTargetManager {
/// Adds a tracked build target with corresponding interested channel.
void addBuildTarget(BuildTarget target, WebSocketChannel channel) {
_buildTargets.putIfAbsent(target, () => <WebSocketChannel>{}).add(channel);
_channelSubscriptions.putIfAbsent(channel, () => {}).add(target);
}

/// Returns channels that are interested in the provided target.
Set<WebSocketChannel> channels(BuildTarget target) =>
_buildTargets[target] ?? <WebSocketChannel>{};

void removeChannel(WebSocketChannel channel) =>
_buildTargets = Map.fromEntries(_buildTargets.entries
.map((e) => MapEntry(e.key, e.value..remove(channel)))
.where((e) => e.value.isNotEmpty));
/// Returns build targets that the [channel] has added.
Iterable<BuildTarget> targetsFor(WebSocketChannel channel) {
return _channelSubscriptions[channel] ?? const Iterable.empty();
}

void removeChannel(WebSocketChannel channel) {
_buildTargets = Map.fromEntries(_buildTargets.entries
.map((e) => MapEntry(e.key, e.value..remove(channel)))
.where((e) => e.value.isNotEmpty));
_channelSubscriptions.remove(channel);
}

Set<BuildTarget> targetsForChanges(List<WatchEvent> changes) =>
targets.where((target) => shouldBuild(target, changes)).toSet();
Expand Down

0 comments on commit f82f3dd

Please sign in to comment.