Skip to content
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

Batch writes #3771

Merged
merged 7 commits into from
Nov 21, 2024
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
3 changes: 3 additions & 0 deletions _test_common/lib/in_memory_writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@ class InMemoryRunnerAssetWriter extends InMemoryAssetWriter
FakeWatcher.notifyWatchers(WatchEvent(
ChangeType.REMOVE, p.absolute(id.package, p.fromUri(id.path))));
}

@override
Future<void> completeBuild() async {}
}
3 changes: 3 additions & 0 deletions _test_common/lib/runner_asset_writer_spy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ class RunnerAssetWriterSpy extends AssetWriterSpy implements RunnerAssetWriter {
_assetsDeleted.add(id);
return _delegate.delete(id);
}

@override
Future<void> completeBuild() async {}
}
6 changes: 2 additions & 4 deletions _test_common/lib/sdk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;

String _dartBinary = p.join(sdkBin, 'dart');

final bool supportsUnsoundNullSafety =
Version.parse(Platform.version.split(' ').first).major == 2;

Expand Down Expand Up @@ -54,13 +52,13 @@ Future<Process> startPub(String package, String command,
/// The [script] should be a relative path under [package].
Future<ProcessResult> runDart(String package, String script,
{Iterable<String>? args}) =>
Process.run(_dartBinary, [script, ...?args],
Process.run(dartBinary, [script, ...?args],
workingDirectory: p.join(d.sandbox, package));

/// Starts the `dart` script [script] in [package] with [args].
///
/// The [script] should be a relative path under [package].
Future<Process> startDart(String package, String script,
{Iterable<String>? args}) =>
Process.start(_dartBinary, [script, ...?args],
Process.start(dartBinary, [script, ...?args],
workingDirectory: p.join(d.sandbox, package));
15 changes: 14 additions & 1 deletion _test_common/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: _test_common
publish_to: none
description: Test infra for writing build tests. Is not published.
resolution: workspace
#resolution: workspace

environment:
sdk: ^3.5.0
Expand All @@ -19,3 +19,16 @@ dependencies:
test: ^1.16.0
test_descriptor: ^2.0.0
watcher: ^1.0.0

dev_dependencies:
dart_flutter_team_lints: ^3.1.0

dependency_overrides:
build:
path: ../build
build_config:
path: ../build_config
build_runner_core:
path: ../build_runner_core
build_test:
path: ../build_test
5 changes: 5 additions & 0 deletions build_runner/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.4.14-wip

- Write generated assets at the end of a build to avoid invalidating other
tools with a file watcher multiple times.

## 2.4.13

- Bump the min sdk to 3.5.0.
Expand Down
5 changes: 5 additions & 0 deletions build_runner/lib/src/watcher/delete_writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ class OnDeleteWriter implements RunnerAssetWriter {
Future writeAsString(AssetId id, String contents,
{Encoding encoding = utf8}) =>
_writer.writeAsString(id, contents, encoding: encoding);

@override
Future<void> completeBuild() async {
await _writer.completeBuild();
}
}
11 changes: 8 additions & 3 deletions build_runner/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: build_runner
version: 2.4.13
version: 2.4.14-wip
description: A build system for Dart code generation and modular compilation.
repository: https://github.com/dart-lang/build/tree/master/build_runner
resolution: workspace
#resolution: workspace

environment:
sdk: ^3.5.0
Expand All @@ -20,7 +20,7 @@ dependencies:
build_config: ">=1.1.0 <1.2.0"
build_daemon: ^4.0.0
build_resolvers: ^2.0.0
build_runner_core: ^7.2.0
build_runner_core: ^8.0.0-wip
code_builder: ^4.2.0
collection: ^1.15.0
crypto: ^3.0.0
Expand Down Expand Up @@ -53,10 +53,15 @@ dev_dependencies:
path: ../_test_common
build_test: ^2.0.0
build_web_compilers: ^4.0.0
dart_flutter_team_lints: ^3.1.0
stream_channel: ^2.0.0
test: ^1.25.5
test_descriptor: ^2.0.0
test_process: ^2.0.0

dependency_overrides:
build_runner_core:
path: ../build_runner_core

topics:
- build-runner
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'dart:isolate';
import 'package:_test_common/sdk.dart';
import 'package:async/async.dart';
import 'package:build/build.dart';
import 'package:build_runner_core/build_runner_core.dart';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as p;
import 'package:stack_trace/stack_trace.dart';
Expand Down Expand Up @@ -106,7 +107,7 @@ Future<BuildTool> package(Iterable<d.Descriptor> otherPackages,
]).create();
await Future.wait(otherPackages.map((d) => d.create()));
await pubGet('a');
return BuildTool._('dart', ['run', 'build_runner']);
return BuildTool._(dartBinary, ['run', 'build_runner']);
}

/// Create a package in [d.sandbox] with a `tool/build.dart` script using
Expand Down Expand Up @@ -145,7 +146,7 @@ Future<BuildTool> packageWithBuildScript(
...contents
]).create();
await pubGet('a');
return BuildTool._('dart', [p.join('tool', 'build.dart')]);
return BuildTool._(dartBinary, [p.join('tool', 'build.dart')]);
}

String _buildersFile(
Expand Down
6 changes: 5 additions & 1 deletion build_runner_core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## 7.3.3-wip
## 8.0.0-wip

- __Breaking__: Add `completeBuild` to `RunnerAssetWriter`, a method expected
to be called by the build system at the end of a completed build.
- Add `wrapInBatch` to obtain a reader/writer pair that will batch writes
before flushing them at the end of a build.
- Bump the min sdk to 3.6.0-dev.228.
- Require analyzer ^6.9.0.
- Fix analyzer deprecations.
Expand Down
1 change: 1 addition & 0 deletions build_runner_core/lib/build_runner_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

export 'package:build/build.dart' show PostProcessBuildStep, PostProcessBuilder;

export 'src/asset/batch.dart' show wrapInBatch;
export 'src/asset/file_based.dart';
export 'src/asset/finalized_reader.dart';
export 'src/asset/reader.dart' show RunnerAssetReader;
Expand Down
163 changes: 163 additions & 0 deletions build_runner_core/lib/src/asset/batch.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright (c) 2024, 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:async';
import 'dart:convert';

import 'package:build/build.dart';
import 'package:glob/glob.dart';
import 'package:meta/meta.dart';

import '../environment/io_environment.dart';
import 'reader.dart';
import 'writer.dart';

/// A batch of file system writes that should be committed at once instead of
/// when [AssetWriter.writeAsBytes] or [AssetWriter.writeAsString] is called.
///
/// During a typical build run emitting generated files one-by-one, it's
/// possible that other running tools such as an analysis server will have to
/// re-analyze incomplete states multiple times.
/// By storing pending outputs in memory first and then committing them at the
/// end of the build, we have a better view over that needs to happen.
///
/// The default [IOEnvironment] uses readers and writes that are batch-aware
/// outside of low-memory mode.
final class _FileSystemWriteBatch {
final Map<AssetId, _PendingFileState> _pendingWrites = {};

_FileSystemWriteBatch._();

Future<void> completeWrites(RunnerAssetWriter writer) async {
await Future.wait(_pendingWrites.keys.map((id) async {
final pending = _pendingWrites[id]!;

if (pending.content case final content?) {
await writer.writeAsBytes(id, content);
} else {
await writer.delete(id);
}
}));

_pendingWrites.clear();
}
}

/// Wraps a pair of a [RunnerAssetReader] with path-prividing capabilities and
/// a [RunnerAssetWriter] into a pair of readers and writers that will
/// internally buffer writes and only flush them in
/// [RunnerAssetWriter.completeBuild].
///
/// The returned reader will see pending writes by the returned writer before
/// they are flushed to the file system.
(RunnerAssetReader, RunnerAssetWriter) wrapInBatch({
required RunnerAssetReader reader,
required PathProvidingAssetReader pathProvidingReader,
required RunnerAssetWriter writer,
}) {
final batch = _FileSystemWriteBatch._();

return (
BatchReader(reader, pathProvidingReader, batch),
BatchWriter(writer, batch),
);
}

final class _PendingFileState {
final List<int>? content;

const _PendingFileState(this.content);

bool get isDeleted => content == null;
}

@internal
final class BatchReader extends AssetReader
implements RunnerAssetReader, PathProvidingAssetReader {
final RunnerAssetReader _inner;
final PathProvidingAssetReader _innerPathProviding;
final _FileSystemWriteBatch _batch;

BatchReader(this._inner, this._innerPathProviding, this._batch);

_PendingFileState? _stateFor(AssetId id) {
return _batch._pendingWrites[id];
}

@override
Future<bool> canRead(AssetId id) async {
if (_stateFor(id) case final state?) {
return !state.isDeleted;
} else {
return await _inner.canRead(id);
}
}

@override
Stream<AssetId> findAssets(Glob glob, {String? package}) {
return _inner
.findAssets(glob, package: package)
.where((asset) => _stateFor(asset)?.isDeleted != true);
}

@override
String pathTo(AssetId id) {
return _innerPathProviding.pathTo(id);
}

@override
Future<List<int>> readAsBytes(AssetId id) async {
if (_stateFor(id) case final state?) {
if (state.isDeleted) {
throw AssetNotFoundException(id);
} else {
return state.content!;
}
} else {
return await _inner.readAsBytes(id);
}
}

@override
Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) async {
if (_stateFor(id) case final state?) {
if (state.isDeleted) {
throw AssetNotFoundException(id);
} else {
return encoding.decode(state.content!);
}
} else {
return await _inner.readAsString(id, encoding: encoding);
}
}
}

@internal
final class BatchWriter extends RunnerAssetWriter {
final RunnerAssetWriter _inner;
final _FileSystemWriteBatch _batch;

BatchWriter(this._inner, this._batch);

@override
Future delete(AssetId id) async {
_batch._pendingWrites[id] = const _PendingFileState(null);
}

@override
Future<void> writeAsBytes(AssetId id, List<int> bytes) async {
_batch._pendingWrites[id] = _PendingFileState(bytes);
}

@override
Future<void> writeAsString(AssetId id, String contents,
{Encoding encoding = utf8}) async {
_batch._pendingWrites[id] = _PendingFileState(encoding.encode(contents));
}

@override
Future<void> completeBuild() async {
await _batch.completeWrites(_inner);
}
}
3 changes: 3 additions & 0 deletions build_runner_core/lib/src/asset/build_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ class BuildCacheWriter implements RunnerAssetWriter {
@override
Future delete(AssetId id) =>
_delegate.delete(_cacheLocation(id, _assetGraph, _rootPackage));

@override
Future<void> completeBuild() async {}
}

AssetId _cacheLocation(AssetId id, AssetGraph assetGraph, String rootPackage) {
Expand Down
3 changes: 3 additions & 0 deletions build_runner_core/lib/src/asset/file_based.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ class FileBasedAssetWriter implements RunnerAssetWriter {
}
});
}

@override
Future<void> completeBuild() async {}
}

/// Returns the path to [id] for a given [packageGraph].
Expand Down
6 changes: 6 additions & 0 deletions build_runner_core/lib/src/asset/writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ typedef OnDelete = void Function(AssetId id);

abstract class RunnerAssetWriter implements AssetWriter {
Future delete(AssetId id);

/// Called after each completed build.
///
/// Some [RunnerAssetWriter] implementations may buffer completed writes
/// internally and flush them in [completeBuild].
Future<void> completeBuild();
}
Loading
Loading